fix: some bugs (#2639)

* fix: invalid index when insert row

* fix: auto fill 0 in front of number start with .

* fix: #2591

* chore: split the update at and create at test

* chore: fix tauri build
This commit is contained in:
Nathan.fooo
2023-05-28 16:14:25 +08:00
committed by GitHub
parent cdfb634aa6
commit 75d40b79d0
29 changed files with 304 additions and 220 deletions

View File

@ -83,13 +83,13 @@ class RowCache {
await _cellCache.dispose(); await _cellCache.dispose();
} }
void applyRowsChanged(RowsChangesetPB changeset) { void applyRowsChanged(RowsChangePB changeset) {
_deleteRows(changeset.deletedRows); _deleteRows(changeset.deletedRows);
_insertRows(changeset.insertedRows); _insertRows(changeset.insertedRows);
_updateRows(changeset.updatedRows); _updateRows(changeset.updatedRows);
} }
void applyRowsVisibility(RowsVisibilityChangesetPB changeset) { void applyRowsVisibility(RowsVisibilityChangePB changeset) {
_hideRows(changeset.invisibleRows); _hideRows(changeset.invisibleRows);
_showRows(changeset.visibleRows); _showRows(changeset.visibleRows);
} }

View File

@ -7,6 +7,7 @@ typedef OnRowChanged = void Function(CellByFieldId, RowsChangedReason);
class RowController { class RowController {
final RowId rowId; final RowId rowId;
final String? groupId;
final String viewId; final String viewId;
final List<VoidCallback> _onRowChangedListeners = []; final List<VoidCallback> _onRowChangedListeners = [];
final RowCache _rowCache; final RowCache _rowCache;
@ -17,6 +18,7 @@ class RowController {
required this.rowId, required this.rowId,
required this.viewId, required this.viewId,
required RowCache rowCache, required RowCache rowCache,
this.groupId,
}) : _rowCache = rowCache; }) : _rowCache = rowCache;
CellByFieldId loadData() { CellByFieldId loadData() {

View File

@ -36,10 +36,16 @@ class RowBackendService {
return DatabaseEventDeleteRow(payload).send(); return DatabaseEventDeleteRow(payload).send();
} }
Future<Either<Unit, FlowyError>> duplicateRow(RowId rowId) { Future<Either<Unit, FlowyError>> duplicateRow({
required RowId rowId,
String? groupId,
}) {
final payload = RowIdPB.create() final payload = RowIdPB.create()
..viewId = viewId ..viewId = viewId
..rowId = rowId; ..rowId = rowId;
if (groupId != null) {
payload.groupId = groupId;
}
return DatabaseEventDuplicateRow(payload).send(); return DatabaseEventDuplicateRow(payload).send();
} }

View File

@ -9,9 +9,9 @@ import 'package:appflowy_backend/protobuf/flowy-database2/notification.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/view_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/view_entities.pb.dart';
typedef RowsVisibilityNotifierValue typedef RowsVisibilityNotifierValue
= Either<RowsVisibilityChangesetPB, FlowyError>; = Either<RowsVisibilityChangePB, FlowyError>;
typedef NumberOfRowsNotifierValue = Either<RowsChangesetPB, FlowyError>; typedef NumberOfRowsNotifierValue = Either<RowsChangePB, FlowyError>;
typedef ReorderAllRowsNotifierValue = Either<List<String>, FlowyError>; typedef ReorderAllRowsNotifierValue = Either<List<String>, FlowyError>;
typedef SingleRowNotifierValue = Either<ReorderSingleRowPB, FlowyError>; typedef SingleRowNotifierValue = Either<ReorderSingleRowPB, FlowyError>;
@ -54,14 +54,14 @@ class DatabaseViewListener {
case DatabaseNotification.DidUpdateViewRowsVisibility: case DatabaseNotification.DidUpdateViewRowsVisibility:
result.fold( result.fold(
(payload) => _rowsVisibility?.value = (payload) => _rowsVisibility?.value =
left(RowsVisibilityChangesetPB.fromBuffer(payload)), left(RowsVisibilityChangePB.fromBuffer(payload)),
(error) => _rowsVisibility?.value = right(error), (error) => _rowsVisibility?.value = right(error),
); );
break; break;
case DatabaseNotification.DidUpdateViewRows: case DatabaseNotification.DidUpdateViewRows:
result.fold( result.fold(
(payload) => (payload) =>
_rowsNotifier?.value = left(RowsChangesetPB.fromBuffer(payload)), _rowsNotifier?.value = left(RowsChangePB.fromBuffer(payload)),
(error) => _rowsNotifier?.value = right(error), (error) => _rowsNotifier?.value = right(error),
); );
break; break;

View File

@ -265,6 +265,7 @@ class _BoardContentState extends State<BoardContent> {
renderHook: renderHook, renderHook: renderHook,
openCard: (context) => _openCard( openCard: (context) => _openCard(
viewId, viewId,
groupData.group.groupId,
fieldController, fieldController,
rowPB, rowPB,
rowCache, rowCache,
@ -302,6 +303,7 @@ class _BoardContentState extends State<BoardContent> {
void _openCard( void _openCard(
String viewId, String viewId,
String groupId,
FieldController fieldController, FieldController fieldController,
RowPB rowPB, RowPB rowPB,
RowCache rowCache, RowCache rowCache,
@ -317,6 +319,7 @@ class _BoardContentState extends State<BoardContent> {
rowId: rowInfo.rowPB.id, rowId: rowInfo.rowPB.id,
viewId: rowInfo.viewId, viewId: rowInfo.viewId,
rowCache: rowCache, rowCache: rowCache,
groupId: groupId,
); );
FlowyOverlay.show( FlowyOverlay.show(
@ -324,7 +327,7 @@ class _BoardContentState extends State<BoardContent> {
builder: (BuildContext context) { builder: (BuildContext context) {
return RowDetailPage( return RowDetailPage(
cellBuilder: GridCellBuilder(cellCache: dataController.cellCache), cellBuilder: GridCellBuilder(cellCache: dataController.cellCache),
dataController: dataController, rowController: dataController,
); );
}, },
); );

View File

@ -241,7 +241,7 @@ void showEventDetails({
cellBuilder: GridCellBuilder( cellBuilder: GridCellBuilder(
cellCache: rowCache.cellCache, cellCache: rowCache.cellCache,
), ),
dataController: dataController, rowController: dataController,
); );
}, },
); );

View File

@ -18,14 +18,14 @@ class RowActionSheetBloc
super(RowActionSheetState.initial(rowInfo)) { super(RowActionSheetState.initial(rowInfo)) {
on<RowActionSheetEvent>( on<RowActionSheetEvent>(
(event, emit) async { (event, emit) async {
await event.map( await event.when(
deleteRow: (_DeleteRow value) async { deleteRow: () async {
final result = await _rowService.deleteRow(state.rowData.rowPB.id); final result = await _rowService.deleteRow(state.rowData.rowPB.id);
logResult(result); logResult(result);
}, },
duplicateRow: (_DuplicateRow value) async { duplicateRow: () async {
final result = final result =
await _rowService.duplicateRow(state.rowData.rowPB.id); await _rowService.duplicateRow(rowId: state.rowData.rowPB.id);
logResult(result); logResult(result);
}, },
); );

View File

@ -38,8 +38,11 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
deleteRow: (rowId) async { deleteRow: (rowId) async {
await rowService.deleteRow(rowId); await rowService.deleteRow(rowId);
}, },
duplicateRow: (String rowId) async { duplicateRow: (String rowId, String? groupId) async {
await rowService.duplicateRow(rowId); await rowService.duplicateRow(
rowId: rowId,
groupId: groupId,
);
}, },
); );
}, },
@ -68,7 +71,8 @@ class RowDetailEvent with _$RowDetailEvent {
const factory RowDetailEvent.initial() = _Initial; const factory RowDetailEvent.initial() = _Initial;
const factory RowDetailEvent.deleteField(String fieldId) = _DeleteField; const factory RowDetailEvent.deleteField(String fieldId) = _DeleteField;
const factory RowDetailEvent.deleteRow(String rowId) = _DeleteRow; const factory RowDetailEvent.deleteRow(String rowId) = _DeleteRow;
const factory RowDetailEvent.duplicateRow(String rowId) = _DuplicateRow; const factory RowDetailEvent.duplicateRow(String rowId, String? groupId) =
_DuplicateRow;
const factory RowDetailEvent.didReceiveCellDatas( const factory RowDetailEvent.didReceiveCellDatas(
List<CellIdentifier> gridCells, List<CellIdentifier> gridCells,
) = _DidReceiveCellDatas; ) = _DidReceiveCellDatas;

View File

@ -348,7 +348,7 @@ class _GridRowsState extends State<_GridRows> {
builder: (BuildContext context) { builder: (BuildContext context) {
return RowDetailPage( return RowDetailPage(
cellBuilder: cellBuilder, cellBuilder: cellBuilder,
dataController: dataController, rowController: dataController,
); );
}, },
); );

View File

@ -26,11 +26,11 @@ import '../../grid/presentation/widgets/header/field_cell.dart';
import '../../grid/presentation/widgets/header/field_editor.dart'; import '../../grid/presentation/widgets/header/field_editor.dart';
class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate { class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
final RowController dataController; final RowController rowController;
final GridCellBuilder cellBuilder; final GridCellBuilder cellBuilder;
const RowDetailPage({ const RowDetailPage({
required this.dataController, required this.rowController,
required this.cellBuilder, required this.cellBuilder,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -49,7 +49,7 @@ class _RowDetailPageState extends State<RowDetailPage> {
return FlowyDialog( return FlowyDialog(
child: BlocProvider( child: BlocProvider(
create: (context) { create: (context) {
return RowDetailBloc(dataController: widget.dataController) return RowDetailBloc(dataController: widget.rowController)
..add(const RowDetailEvent.initial()); ..add(const RowDetailEvent.initial());
}, },
child: ListView( child: ListView(
@ -69,11 +69,11 @@ class _RowDetailPageState extends State<RowDetailPage> {
Widget _responsiveRowInfo() { Widget _responsiveRowInfo() {
final rowDataColumn = _PropertyColumn( final rowDataColumn = _PropertyColumn(
cellBuilder: widget.cellBuilder, cellBuilder: widget.cellBuilder,
viewId: widget.dataController.viewId, viewId: widget.rowController.viewId,
); );
final rowOptionColumn = _RowOptionColumn( final rowOptionColumn = _RowOptionColumn(
viewId: widget.dataController.viewId, viewId: widget.rowController.viewId,
rowId: widget.dataController.rowId, rowController: widget.rowController,
); );
if (MediaQuery.of(context).size.width > 800) { if (MediaQuery.of(context).size.width > 800) {
return Row( return Row(
@ -372,10 +372,10 @@ GridCellStyle? _customCellStyle(FieldType fieldType) {
} }
class _RowOptionColumn extends StatelessWidget { class _RowOptionColumn extends StatelessWidget {
final String rowId; final RowController rowController;
const _RowOptionColumn({ const _RowOptionColumn({
required String viewId, required String viewId,
required this.rowId, required this.rowController,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -390,8 +390,11 @@ class _RowOptionColumn extends StatelessWidget {
child: FlowyText(LocaleKeys.grid_row_action.tr()), child: FlowyText(LocaleKeys.grid_row_action.tr()),
), ),
const VSpace(15), const VSpace(15),
_DeleteButton(rowId: rowId), _DeleteButton(rowId: rowController.rowId),
_DuplicateButton(rowId: rowId), _DuplicateButton(
rowId: rowController.rowId,
groupId: rowController.groupId,
),
], ],
); );
} }
@ -419,7 +422,12 @@ class _DeleteButton extends StatelessWidget {
class _DuplicateButton extends StatelessWidget { class _DuplicateButton extends StatelessWidget {
final String rowId; final String rowId;
const _DuplicateButton({required this.rowId, Key? key}) : super(key: key); final String? groupId;
const _DuplicateButton({
required this.rowId,
this.groupId,
Key? key,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -429,7 +437,9 @@ class _DuplicateButton extends StatelessWidget {
text: FlowyText.regular(LocaleKeys.grid_row_duplicate.tr()), text: FlowyText.regular(LocaleKeys.grid_row_duplicate.tr()),
leftIcon: const FlowySvg(name: "grid/duplicate"), leftIcon: const FlowySvg(name: "grid/duplicate"),
onTap: () { onTap: () {
context.read<RowDetailBloc>().add(RowDetailEvent.duplicateRow(rowId)); context
.read<RowDetailBloc>()
.add(RowDetailEvent.duplicateRow(rowId, groupId));
FlowyOverlay.pop(context); FlowyOverlay.pop(context);
}, },
), ),

View File

@ -99,7 +99,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -1023,7 +1023,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -1040,7 +1040,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"bytes", "bytes",
"collab-sync", "collab-sync",
@ -1058,7 +1058,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1083,7 +1083,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1095,7 +1095,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -1112,7 +1112,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -1130,7 +1130,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"bincode", "bincode",
"chrono", "chrono",
@ -1150,7 +1150,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1180,7 +1180,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"bytes", "bytes",
"collab", "collab",

View File

@ -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 = "d6af3a" } collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d6af3a" } collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d6af3a" } collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d6af3a" } collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d6af3a" } collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" }
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d6af3a" } appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" }
#collab = { path = "../../AppFlowy-Collab/collab" } #collab = { path = "../../AppFlowy-Collab/collab" }
#collab-folder = { path = "../../AppFlowy-Collab/collab-folder" } #collab-folder = { path = "../../AppFlowy-Collab/collab-folder" }

View File

@ -4,17 +4,17 @@ import {
UpdatedRowPB, UpdatedRowPB,
RowIdPB, RowIdPB,
OptionalRowPB, OptionalRowPB,
RowsChangesetPB, RowsChangePB,
RowsVisibilityChangesetPB, RowsVisibilityChangePB,
ReorderSingleRowPB ReorderSingleRowPB,
} from "@/services/backend"; } from '@/services/backend';
import { ChangeNotifier } from "$app/utils/change_notifier"; import { ChangeNotifier } from '$app/utils/change_notifier';
import { FieldInfo } from "../field/field_controller"; import { FieldInfo } from '../field/field_controller';
import { CellCache, CellCacheKey } from "../cell/cell_cache"; import { CellCache, CellCacheKey } from '../cell/cell_cache';
import { CellIdentifier } from "../cell/cell_bd_svc"; import { CellIdentifier } from '../cell/cell_bd_svc';
import { DatabaseEventGetRow } from "@/services/backend/events/flowy-database2"; import { DatabaseEventGetRow } from '@/services/backend/events/flowy-database2';
import { None, Option, Some } from "ts-results"; import { None, Option, Some } from 'ts-results';
import { Log } from "$app/utils/log"; import { Log } from '$app/utils/log';
export type CellByFieldId = Map<string, CellIdentifier>; export type CellByFieldId = Map<string, CellIdentifier>;
@ -82,13 +82,13 @@ export class RowCache {
this.notifier.withChange(RowChangedReason.ReorderRows); this.notifier.withChange(RowChangedReason.ReorderRows);
}; };
applyRowsChanged = (changeset: RowsChangesetPB) => { applyRowsChanged = (changeset: RowsChangePB) => {
this._deleteRows(changeset.deleted_rows); this._deleteRows(changeset.deleted_rows);
this._insertRows(changeset.inserted_rows); this._insertRows(changeset.inserted_rows);
this._updateRows(changeset.updated_rows); this._updateRows(changeset.updated_rows);
}; };
applyRowsVisibility = (changeset: RowsVisibilityChangesetPB) => { applyRowsVisibility = (changeset: RowsVisibilityChangePB) => {
this._hideRows(changeset.invisible_rows); this._hideRows(changeset.invisible_rows);
this._displayRows(changeset.visible_rows); this._displayRows(changeset.visible_rows);
}; };
@ -339,8 +339,7 @@ export class RowInfo {
public readonly viewId: string, public readonly viewId: string,
public readonly fieldInfos: readonly FieldInfo[], public readonly fieldInfos: readonly FieldInfo[],
public readonly row: RowPB public readonly row: RowPB
) { ) {}
}
copyWith = (params: { row?: RowPB; fieldInfos?: readonly FieldInfo[] }) => { copyWith = (params: { row?: RowPB; fieldInfos?: readonly FieldInfo[] }) => {
return new RowInfo(this.viewId, params.fieldInfos || this.fieldInfos, params.row || this.row); return new RowInfo(this.viewId, params.fieldInfos || this.fieldInfos, params.row || this.row);
@ -348,18 +347,15 @@ export class RowInfo {
} }
export class DeletedRow { export class DeletedRow {
constructor(public readonly index: number, public readonly rowInfo: RowInfo) { constructor(public readonly index: number, public readonly rowInfo: RowInfo) {}
}
} }
export class InsertedRow { export class InsertedRow {
constructor(public readonly index: number, public readonly rowId: string) { constructor(public readonly index: number, public readonly rowId: string) {}
}
} }
export class RowChanged { export class RowChanged {
constructor(public readonly reason: RowChangedReason, public readonly rowId?: string) { constructor(public readonly reason: RowChangedReason, public readonly rowId?: string) {}
}
} }
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow

View File

@ -1,17 +1,17 @@
import { Ok, Result } from "ts-results"; import { Ok, Result } from 'ts-results';
import { import {
DatabaseNotification, DatabaseNotification,
FlowyError, FlowyError,
ReorderAllRowsPB, ReorderAllRowsPB,
ReorderSingleRowPB, ReorderSingleRowPB,
RowsChangesetPB, RowsChangePB,
RowsVisibilityChangesetPB RowsVisibilityChangePB,
} from "@/services/backend"; } from '@/services/backend';
import { ChangeNotifier } from "$app/utils/change_notifier"; import { ChangeNotifier } from '$app/utils/change_notifier';
import { DatabaseNotificationObserver } from "../notifications/observer"; import { DatabaseNotificationObserver } from '../notifications/observer';
export type RowsVisibilityNotifyValue = Result<RowsVisibilityChangesetPB, FlowyError>; export type RowsVisibilityNotifyValue = Result<RowsVisibilityChangePB, FlowyError>;
export type RowsNotifyValue = Result<RowsChangesetPB, FlowyError>; export type RowsNotifyValue = Result<RowsChangePB, FlowyError>;
export type ReorderRowsNotifyValue = Result<string[], FlowyError>; export type ReorderRowsNotifyValue = Result<string[], FlowyError>;
export type ReorderSingleRowNotifyValue = Result<ReorderSingleRowPB, FlowyError>; export type ReorderSingleRowNotifyValue = Result<ReorderSingleRowPB, FlowyError>;
@ -23,8 +23,7 @@ export class DatabaseViewRowsObserver {
private _listener?: DatabaseNotificationObserver; private _listener?: DatabaseNotificationObserver;
constructor(public readonly viewId: string) { constructor(public readonly viewId: string) {}
}
subscribe = async (callbacks: { subscribe = async (callbacks: {
onRowsVisibilityChanged?: (value: RowsVisibilityNotifyValue) => void; onRowsVisibilityChanged?: (value: RowsVisibilityNotifyValue) => void;
@ -44,14 +43,14 @@ export class DatabaseViewRowsObserver {
switch (notification) { switch (notification) {
case DatabaseNotification.DidUpdateViewRowsVisibility: case DatabaseNotification.DidUpdateViewRowsVisibility:
if (result.ok) { if (result.ok) {
this.rowsVisibilityNotifier.notify(Ok(RowsVisibilityChangesetPB.deserializeBinary(result.val))); this.rowsVisibilityNotifier.notify(Ok(RowsVisibilityChangePB.deserializeBinary(result.val)));
} else { } else {
this.rowsVisibilityNotifier.notify(result); this.rowsVisibilityNotifier.notify(result);
} }
break; break;
case DatabaseNotification.DidUpdateViewRows: case DatabaseNotification.DidUpdateViewRows:
if (result.ok) { if (result.ok) {
this.rowsNotifier.notify(Ok(RowsChangesetPB.deserializeBinary(result.val))); this.rowsNotifier.notify(Ok(RowsChangePB.deserializeBinary(result.val)));
} else { } else {
this.rowsNotifier.notify(result); this.rowsNotifier.notify(result);
} }
@ -73,7 +72,7 @@ export class DatabaseViewRowsObserver {
default: default:
break; break;
} }
} },
}); });
await this._listener.start(); await this._listener.start();
}; };

View File

@ -85,7 +85,7 @@ checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
[[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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -886,7 +886,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -903,7 +903,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"bytes", "bytes",
"collab-sync", "collab-sync",
@ -921,7 +921,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -946,7 +946,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -958,7 +958,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -975,7 +975,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -993,7 +993,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"bincode", "bincode",
"chrono", "chrono",
@ -1013,7 +1013,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1043,7 +1043,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=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
dependencies = [ dependencies = [
"bytes", "bytes",
"collab", "collab",

View File

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

View File

@ -142,11 +142,15 @@ pub struct RowIdPB {
#[pb(index = 2)] #[pb(index = 2)]
pub row_id: String, pub row_id: String,
#[pb(index = 3, one_of)]
pub group_id: Option<String>,
} }
pub struct RowIdParams { pub struct RowIdParams {
pub view_id: String, pub view_id: String,
pub row_id: RowId, pub row_id: RowId,
pub group_id: Option<String>,
} }
impl TryInto<RowIdParams> for RowIdPB { impl TryInto<RowIdParams> for RowIdPB {
@ -154,10 +158,19 @@ impl TryInto<RowIdParams> for RowIdPB {
fn try_into(self) -> Result<RowIdParams, Self::Error> { fn try_into(self) -> Result<RowIdParams, Self::Error> {
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
let group_id = match self.group_id {
Some(group_id) => Some(
NotEmptyStr::parse(group_id)
.map_err(|_| ErrorCode::GroupIdIsEmpty)?
.0,
),
None => None,
};
Ok(RowIdParams { Ok(RowIdParams {
view_id: view_id.0, view_id: view_id.0,
row_id: RowId::from(self.row_id), row_id: RowId::from(self.row_id),
group_id,
}) })
} }
} }

View File

@ -3,7 +3,7 @@ use flowy_derive::ProtoBuf;
use crate::entities::{InsertedRowPB, UpdatedRowPB}; use crate::entities::{InsertedRowPB, UpdatedRowPB};
#[derive(Debug, Default, Clone, ProtoBuf)] #[derive(Debug, Default, Clone, ProtoBuf)]
pub struct RowsVisibilityChangesetPB { pub struct RowsVisibilityChangePB {
#[pb(index = 1)] #[pb(index = 1)]
pub view_id: String, pub view_id: String,
@ -15,7 +15,7 @@ pub struct RowsVisibilityChangesetPB {
} }
#[derive(Debug, Default, Clone, ProtoBuf)] #[derive(Debug, Default, Clone, ProtoBuf)]
pub struct RowsChangesetPB { pub struct RowsChangePB {
#[pb(index = 1)] #[pb(index = 1)]
pub view_id: String, pub view_id: String,
@ -29,27 +29,27 @@ pub struct RowsChangesetPB {
pub updated_rows: Vec<UpdatedRowPB>, pub updated_rows: Vec<UpdatedRowPB>,
} }
impl RowsChangesetPB { impl RowsChangePB {
pub fn from_insert(view_id: String, inserted_rows: Vec<InsertedRowPB>) -> Self { pub fn from_insert(view_id: String, inserted_row: InsertedRowPB) -> Self {
Self { Self {
view_id, view_id,
inserted_rows, inserted_rows: vec![inserted_row],
..Default::default() ..Default::default()
} }
} }
pub fn from_delete(view_id: String, deleted_rows: Vec<String>) -> Self { pub fn from_delete(view_id: String, deleted_row: String) -> Self {
Self { Self {
view_id, view_id,
deleted_rows, deleted_rows: vec![deleted_row],
..Default::default() ..Default::default()
} }
} }
pub fn from_update(view_id: String, updated_rows: Vec<UpdatedRowPB>) -> Self { pub fn from_update(view_id: String, updated_row: UpdatedRowPB) -> Self {
Self { Self {
view_id, view_id,
updated_rows, updated_rows: vec![updated_row],
..Default::default() ..Default::default()
} }
} }

View File

@ -1,13 +1,16 @@
use collab_database::database::gen_row_id;
use std::sync::Arc; use std::sync::Arc;
use collab_database::rows::RowId; use collab_database::rows::RowId;
use collab_database::views::DatabaseLayout; use collab_database::views::DatabaseLayout;
use lib_infra::util::timestamp;
use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult}; use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
use crate::entities::*; use crate::entities::*;
use crate::manager::DatabaseManager2; use crate::manager::DatabaseManager2;
use crate::services::cell::CellBuilder;
use crate::services::field::{ use crate::services::field::{
type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset, type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset,
@ -304,7 +307,7 @@ pub(crate) async fn duplicate_row_handler(
let params: RowIdParams = data.into_inner().try_into()?; let params: RowIdParams = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&params.view_id).await?; let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
database_editor database_editor
.duplicate_row(&params.view_id, &params.row_id) .duplicate_row(&params.view_id, params.group_id, &params.row_id)
.await; .await;
Ok(()) Ok(())
} }
@ -329,7 +332,23 @@ pub(crate) async fn create_row_handler(
) -> DataResult<RowPB, FlowyError> { ) -> DataResult<RowPB, FlowyError> {
let params: CreateRowParams = data.into_inner().try_into()?; let params: CreateRowParams = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&params.view_id).await?; let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
match database_editor.create_row(params).await? { let fields = database_editor.get_fields(&params.view_id, None);
let cells =
CellBuilder::with_cells(params.cell_data_by_field_id.unwrap_or_default(), &fields).build();
let view_id = params.view_id;
let group_id = params.group_id;
let params = collab_database::rows::CreateRowParams {
id: gen_row_id(),
cells,
height: 60,
visibility: true,
prev_row_id: params.start_row_id,
timestamp: timestamp(),
};
match database_editor
.create_row(&view_id, group_id, params)
.await?
{
None => Err(FlowyError::internal().context("Create row fail")), None => Err(FlowyError::internal().context("Create row fail")),
Some(row) => data_result_ok(RowPB::from(row)), Some(row) => data_result_ok(RowPB::from(row)),
} }

View File

@ -3,9 +3,9 @@ use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use bytes::Bytes; use bytes::Bytes;
use collab_database::database::{gen_row_id, timestamp, Database as InnerDatabase}; use collab_database::database::{timestamp, Database as InnerDatabase};
use collab_database::fields::{Field, TypeOptionData}; use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{Cell, Cells, Row, RowCell, RowId}; use collab_database::rows::{Cell, Cells, CreateRowParams, Row, RowCell, RowId};
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting}; use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
use parking_lot::Mutex; use parking_lot::Mutex;
use tokio::sync::{broadcast, RwLock}; use tokio::sync::{broadcast, RwLock};
@ -16,14 +16,14 @@ use lib_infra::future::{to_fut, Fut};
use crate::entities::{ use crate::entities::{
AlterFilterParams, AlterSortParams, CalendarEventPB, CellChangesetNotifyPB, CellPB, AlterFilterParams, AlterSortParams, CalendarEventPB, CellChangesetNotifyPB, CellPB,
CreateRowParams, DatabaseFieldChangesetPB, DatabasePB, DatabaseViewSettingPB, DeleteFilterParams, DatabaseFieldChangesetPB, DatabasePB, DatabaseViewSettingPB, DeleteFilterParams,
DeleteGroupParams, DeleteSortParams, FieldChangesetParams, FieldIdPB, FieldPB, FieldType, DeleteGroupParams, DeleteSortParams, FieldChangesetParams, FieldIdPB, FieldPB, FieldType,
GroupPB, IndexFieldPB, InsertGroupParams, InsertedRowPB, LayoutSettingParams, RepeatedFilterPB, GroupPB, IndexFieldPB, InsertGroupParams, InsertedRowPB, LayoutSettingParams, RepeatedFilterPB,
RepeatedGroupPB, RepeatedSortPB, RowPB, RowsChangesetPB, SelectOptionCellDataPB, SelectOptionPB, RepeatedGroupPB, RepeatedSortPB, RowPB, RowsChangePB, SelectOptionCellDataPB, SelectOptionPB,
}; };
use crate::notification::{send_notification, DatabaseNotification}; use crate::notification::{send_notification, DatabaseNotification};
use crate::services::cell::{ use crate::services::cell::{
apply_cell_changeset, get_cell_protobuf, insert_date_cell, AnyTypeCache, CellBuilder, CellCache, apply_cell_changeset, get_cell_protobuf, insert_date_cell, AnyTypeCache, CellCache,
ToCellChangeset, ToCellChangeset,
}; };
use crate::services::database::util::database_view_setting_pb_from_view; use crate::services::database::util::database_view_setting_pb_from_view;
@ -274,8 +274,17 @@ impl DatabaseEditor {
Ok(()) Ok(())
} }
pub async fn duplicate_row(&self, view_id: &str, row_id: &RowId) { // consider returning a result. But most of the time, it should be fine to just ignore the error.
let _ = self.database.lock().duplicate_row(view_id, row_id); pub async fn duplicate_row(&self, view_id: &str, group_id: Option<String>, row_id: &RowId) {
let params = self.database.lock().duplicate_row(row_id);
match params {
None => {
tracing::warn!("Failed to duplicate row: {}", row_id);
},
Some(params) => {
let _ = self.create_row(view_id, group_id, params).await;
},
}
} }
pub async fn move_row(&self, view_id: &str, from: RowId, to: RowId) { pub async fn move_row(&self, view_id: &str, from: RowId, to: RowId) {
@ -292,39 +301,30 @@ impl DatabaseEditor {
let delete_row_id = from.into_inner(); let delete_row_id = from.into_inner();
let insert_row = InsertedRowPB::from(&row).with_index(to_index as i32); let insert_row = InsertedRowPB::from(&row).with_index(to_index as i32);
let changeset = let changes =
RowsChangesetPB::from_move(view_id.to_string(), 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(changeset) .payload(changes)
.send(); .send();
} }
} }
pub async fn create_row(&self, params: CreateRowParams) -> FlowyResult<Option<Row>> { pub async fn create_row(
let fields = self.database.lock().get_fields(&params.view_id, None); &self,
let mut cells = view_id: &str,
CellBuilder::with_cells(params.cell_data_by_field_id.unwrap_or_default(), &fields).build(); group_id: Option<String>,
mut params: CreateRowParams,
) -> FlowyResult<Option<Row>> {
for view in self.database_views.editors().await { for view in self.database_views.editors().await {
view.v_will_create_row(&mut cells, &params.group_id).await; view.v_will_create_row(&mut params.cells, &group_id).await;
} }
let result = self.database.lock().create_row_in_view(view_id, params);
let result = self.database.lock().create_row_in_view(
&params.view_id,
collab_database::rows::CreateRowParams {
id: gen_row_id(),
cells,
height: 60,
visibility: true,
prev_row_id: params.start_row_id,
timestamp: timestamp(),
},
);
if let Some((index, row_order)) = result { if let Some((index, row_order)) = result {
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);
if let Some(row) = row { if let Some(row) = row {
for view in self.database_views.editors().await { for view in self.database_views.editors().await {
view.v_did_create_row(&row, &params.group_id, index).await; view.v_did_create_row(&row, &group_id, index).await;
} }
return Ok(Some(row)); return Ok(Some(row));
} }

View File

@ -1,7 +1,7 @@
#![allow(clippy::while_let_loop)] #![allow(clippy::while_let_loop)]
use crate::entities::{ use crate::entities::{
DatabaseViewSettingPB, FilterChangesetNotificationPB, GroupChangesetPB, GroupRowsNotificationPB, DatabaseViewSettingPB, FilterChangesetNotificationPB, GroupChangesetPB, GroupRowsNotificationPB,
ReorderAllRowsPB, ReorderSingleRowPB, RowsVisibilityChangesetPB, SortChangesetNotificationPB, ReorderAllRowsPB, ReorderSingleRowPB, RowsVisibilityChangePB, SortChangesetNotificationPB,
}; };
use crate::notification::{send_notification, DatabaseNotification}; use crate::notification::{send_notification, DatabaseNotification};
use crate::services::filter::FilterResultNotification; use crate::services::filter::FilterResultNotification;
@ -38,7 +38,7 @@ impl DatabaseViewChangedReceiverRunner {
.for_each(|changed| async { .for_each(|changed| async {
match changed { match changed {
DatabaseViewChanged::FilterNotification(notification) => { DatabaseViewChanged::FilterNotification(notification) => {
let changeset = RowsVisibilityChangesetPB { let changeset = RowsVisibilityChangePB {
view_id: notification.view_id, view_id: notification.view_id,
visible_rows: notification.visible_rows, visible_rows: notification.visible_rows,
invisible_rows: notification invisible_rows: notification

View File

@ -16,7 +16,7 @@ use crate::entities::{
AlterFilterParams, AlterSortParams, CalendarEventPB, DeleteFilterParams, DeleteGroupParams, AlterFilterParams, AlterSortParams, CalendarEventPB, DeleteFilterParams, DeleteGroupParams,
DeleteSortParams, FieldType, GroupChangesetPB, GroupPB, GroupRowsNotificationPB, DeleteSortParams, FieldType, GroupChangesetPB, GroupPB, GroupRowsNotificationPB,
InsertGroupParams, InsertedGroupPB, InsertedRowPB, LayoutSettingPB, LayoutSettingParams, RowPB, InsertGroupParams, InsertedGroupPB, InsertedRowPB, LayoutSettingPB, LayoutSettingParams, RowPB,
RowsChangesetPB, SortChangesetNotificationPB, SortPB, RowsChangePB, SortChangesetNotificationPB, SortPB,
}; };
use crate::notification::{send_notification, DatabaseNotification}; use crate::notification::{send_notification, DatabaseNotification};
use crate::services::cell::CellCache; use crate::services::cell::CellCache;
@ -182,9 +182,10 @@ impl DatabaseViewEditor {
// Send the group notification if the current view has groups // Send the group notification if the current view has groups
match group_id.as_ref() { match group_id.as_ref() {
None => { None => {
let changeset = RowsChangesetPB::from_insert(self.view_id.clone(), vec![row.into()]); let row = InsertedRowPB::from(row).with_index(index as i32);
let changes = RowsChangePB::from_insert(self.view_id.clone(), row);
send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows) send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
.payload(changeset) .payload(changes)
.send(); .send();
}, },
Some(group_id) => { Some(group_id) => {
@ -219,16 +220,15 @@ impl DatabaseViewEditor {
notify_did_update_group_rows(changeset).await; notify_did_update_group_rows(changeset).await;
} }
} }
let changeset = let changes = RowsChangePB::from_delete(self.view_id.clone(), row.id.clone().into_inner());
RowsChangesetPB::from_delete(self.view_id.clone(), vec![row.id.clone().into_inner()]);
send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows) send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
.payload(changeset) .payload(changes)
.send(); .send();
} }
/// Notify the view that the row has been updated. If the view has groups, /// Notify the view that the row has been updated. If the view has groups,
/// send the group notification with [GroupRowsNotificationPB]. Otherwise, /// send the group notification with [GroupRowsNotificationPB]. Otherwise,
/// send the view notification with [RowsChangesetPB] /// send the view notification with [RowsChangePB]
pub async fn v_did_update_row(&self, old_row: &Option<Row>, row: &Row, field_id: &str) { pub async fn v_did_update_row(&self, old_row: &Option<Row>, row: &Row, field_id: &str) {
let result = self let result = self
.mut_group_controller(|group_controller, field| { .mut_group_controller(|group_controller, field| {
@ -263,7 +263,7 @@ impl DatabaseViewEditor {
row: RowOrder::from(row), row: RowOrder::from(row),
field_ids: vec![field_id.to_string()], field_ids: vec![field_id.to_string()],
}; };
let changeset = RowsChangesetPB::from_update(self.view_id.clone(), vec![update_row.into()]); let changeset = RowsChangePB::from_update(self.view_id.clone(), update_row.into());
send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows) send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
.payload(changeset) .payload(changeset)
.send(); .send();
@ -784,18 +784,18 @@ 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) => {
RowsChangesetPB::from_insert(self.view_id.clone(), vec![row.into()]) RowsChangePB::from_insert(self.view_id.clone(), row.into())
}, },
DatabaseRowEvent::UpdateRow(row) => { DatabaseRowEvent::UpdateRow(row) => {
RowsChangesetPB::from_update(self.view_id.clone(), vec![row.into()]) RowsChangePB::from_update(self.view_id.clone(), row.into())
}, },
DatabaseRowEvent::DeleteRow(row_id) => { DatabaseRowEvent::DeleteRow(row_id) => {
RowsChangesetPB::from_delete(self.view_id.clone(), vec![row_id.into_inner()]) 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,
} => RowsChangesetPB::from_move( } => RowsChangePB::from_move(
self.view_id.clone(), self.view_id.clone(),
vec![deleted_row_id.into_inner()], vec![deleted_row_id.into_inner()],
vec![inserted_row.into()], vec![inserted_row.into()],

View File

@ -16,8 +16,6 @@ mod tests {
// Input is empty String // Input is empty String
assert_number(&type_option, "", "", &field_type, &field); assert_number(&type_option, "", "", &field_type, &field);
// Input is letter
assert_number(&type_option, "abc", "", &field_type, &field); assert_number(&type_option, "abc", "", &field_type, &field);
assert_number(&type_option, "-123", "-123", &field_type, &field); assert_number(&type_option, "-123", "-123", &field_type, &field);
assert_number(&type_option, "abc-123", "-123", &field_type, &field); assert_number(&type_option, "abc-123", "-123", &field_type, &field);
@ -25,6 +23,7 @@ mod tests {
assert_number(&type_option, "0.2", "0.2", &field_type, &field); assert_number(&type_option, "0.2", "0.2", &field_type, &field);
assert_number(&type_option, "-0.2", "-0.2", &field_type, &field); assert_number(&type_option, "-0.2", "-0.2", &field_type, &field);
assert_number(&type_option, "-$0.2", "0.2", &field_type, &field); assert_number(&type_option, "-$0.2", "0.2", &field_type, &field);
assert_number(&type_option, ".2", "0.2", &field_type, &field);
} }
#[test] #[test]
@ -42,6 +41,7 @@ mod tests {
assert_number(&type_option, "-0.2", "-$0.2", &field_type, &field); assert_number(&type_option, "-0.2", "-$0.2", &field_type, &field);
assert_number(&type_option, "-$0.2", "-$0.2", &field_type, &field); assert_number(&type_option, "-$0.2", "-$0.2", &field_type, &field);
assert_number(&type_option, "-€0.2", "-$0.2", &field_type, &field); assert_number(&type_option, "-€0.2", "-$0.2", &field_type, &field);
assert_number(&type_option, ".2", "$0.2", &field_type, &field);
} }
#[test] #[test]

View File

@ -128,12 +128,25 @@ impl NumberTypeOption {
Err(_) => Ok(NumberCellFormat::new()), Err(_) => Ok(NumberCellFormat::new()),
} }
} else { } else {
let num_str = match EXTRACT_NUM_REGEX.captures(&num_cell_data.0) { // Test the input string is start with dot and only contains number.
Ok(Some(captures)) => captures // If it is, add a 0 before the dot. For example, ".123" -> "0.123"
.get(0) let num_str = match START_WITH_DOT_NUM_REGEX.captures(&num_cell_data.0) {
.map(|m| m.as_str().to_string()) Ok(Some(captures)) => match captures.get(0).map(|m| m.as_str().to_string()) {
.unwrap_or_default(), Some(s) => {
_ => "".to_string(), format!("0{}", s)
},
None => "".to_string(),
},
// Extract the number from the string.
// For example, "123abc" -> "123". check out the number_type_option_input_test test for
// more examples.
_ => match EXTRACT_NUM_REGEX.captures(&num_cell_data.0) {
Ok(Some(captures)) => captures
.get(0)
.map(|m| m.as_str().to_string())
.unwrap_or_default(),
_ => "".to_string(),
},
}; };
match Decimal::from_str(&num_str) { match Decimal::from_str(&num_str) {
@ -142,7 +155,10 @@ impl NumberTypeOption {
} }
} }
}, },
_ => NumberCellFormat::from_format_str(&num_cell_data.0, &self.format), _ => {
// If the format is not number, use the format string to format the number.
NumberCellFormat::from_format_str(&num_cell_data.0, &self.format)
},
} }
} }
@ -261,4 +277,5 @@ impl std::default::Default for NumberTypeOption {
lazy_static! { lazy_static! {
static ref SCIENTIFIC_NOTATION_REGEX: Regex = Regex::new(r"([+-]?\d*\.?\d+)e([+-]?\d+)").unwrap(); static ref SCIENTIFIC_NOTATION_REGEX: Regex = Regex::new(r"([+-]?\d*\.?\d+)e([+-]?\d+)").unwrap();
pub(crate) static ref EXTRACT_NUM_REGEX: Regex = Regex::new(r"-?\d+(\.\d+)?").unwrap(); pub(crate) static ref EXTRACT_NUM_REGEX: Regex = Regex::new(r"-?\d+(\.\d+)?").unwrap();
pub(crate) static ref START_WITH_DOT_NUM_REGEX: Regex = Regex::new(r"^\.\d+").unwrap();
} }

View File

@ -1,6 +1,6 @@
use crate::services::cell::{CellBytesCustomParser, CellProtobufBlobParser, DecodedCellData}; use crate::services::cell::{CellBytesCustomParser, CellProtobufBlobParser, DecodedCellData};
use crate::services::field::number_currency::Currency; use crate::services::field::number_currency::Currency;
use crate::services::field::{NumberFormat, EXTRACT_NUM_REGEX}; use crate::services::field::{NumberFormat, EXTRACT_NUM_REGEX, START_WITH_DOT_NUM_REGEX};
use bytes::Bytes; use bytes::Bytes;
use flowy_error::FlowyResult; use flowy_error::FlowyResult;
use rust_decimal::Decimal; use rust_decimal::Decimal;
@ -32,8 +32,8 @@ impl NumberCellFormat {
Some(offset) => offset != 0, Some(offset) => offset != 0,
}; };
// Extract number from string. let num_str = auto_fill_zero_at_start_if_need(num_str);
let num_str = extract_number(num_str); let num_str = extract_number(&num_str);
match Decimal::from_str(&num_str) { match Decimal::from_str(&num_str) {
Ok(mut decimal) => { Ok(mut decimal) => {
decimal.set_sign_positive(sign_positive); decimal.set_sign_positive(sign_positive);
@ -70,6 +70,16 @@ impl NumberCellFormat {
} }
} }
fn auto_fill_zero_at_start_if_need(num_str: &str) -> String {
match START_WITH_DOT_NUM_REGEX.captures(num_str) {
Ok(Some(captures)) => match captures.get(0).map(|m| m.as_str().to_string()) {
Some(s) => format!("0{}", s),
None => num_str.to_string(),
},
_ => num_str.to_string(),
}
}
fn extract_number(num_str: &str) -> String { fn extract_number(num_str: &str) -> String {
let mut matches = EXTRACT_NUM_REGEX.find_iter(num_str); let mut matches = EXTRACT_NUM_REGEX.find_iter(num_str);
let mut values = vec![]; let mut values = vec![];

View File

@ -2,52 +2,48 @@ use crate::database::block_test::script::DatabaseRowTest;
use crate::database::block_test::script::RowScript::*; use crate::database::block_test::script::RowScript::*;
use flowy_database2::entities::FieldType; use flowy_database2::entities::FieldType;
use flowy_database2::services::field::DateCellData; use flowy_database2::services::field::DateCellData;
use lib_infra::util::timestamp;
// Create a new row at the end of the grid and check the create time is valid.
#[tokio::test] #[tokio::test]
async fn set_created_at_field_on_create_row() { async fn created_at_field_test() {
let mut test = DatabaseRowTest::new().await; let mut test = DatabaseRowTest::new().await;
let row_count = test.rows.len(); let row_count = test.rows.len();
let before_create_timestamp = chrono::offset::Utc::now().timestamp();
test test
.run_scripts(vec![CreateEmptyRow, AssertRowCount(row_count + 1)]) .run_scripts(vec![CreateEmptyRow, AssertRowCount(row_count + 1)])
.await; .await;
let after_create_timestamp = chrono::offset::Utc::now().timestamp();
let mut rows = test.rows.clone(); // Get created time of the new row.
rows.sort_by(|r1, r2| r1.created_at.cmp(&r2.created_at)); let row = test.get_rows().await.last().cloned().unwrap();
let row = rows.last().unwrap(); let updated_at_field = test.get_first_field(FieldType::CreatedAt);
let cell = row.cells.cell_for_field_id(&updated_at_field.id).unwrap();
let fields = test.fields.clone();
let created_at_field = fields
.iter()
.find(|&f| FieldType::from(f.field_type) == FieldType::CreatedAt)
.unwrap();
let cell = row.cells.cell_for_field_id(&created_at_field.id).unwrap();
let created_at_timestamp = DateCellData::from(cell).timestamp.unwrap(); let created_at_timestamp = DateCellData::from(cell).timestamp.unwrap();
assert!( assert!(created_at_timestamp > 0);
created_at_timestamp >= before_create_timestamp assert!(created_at_timestamp < timestamp());
&& created_at_timestamp <= after_create_timestamp, }
"timestamp: {}, before: {}, after: {}",
created_at_timestamp, // Update row and check the update time is valid.
before_create_timestamp, #[tokio::test]
after_create_timestamp async fn update_at_field_test() {
); let mut test = DatabaseRowTest::new().await;
let row = test.get_rows().await.remove(0);
let updated_at_field = fields let updated_at_field = test.get_first_field(FieldType::UpdatedAt);
.iter() let cell = row.cells.cell_for_field_id(&updated_at_field.id).unwrap();
.find(|&f| FieldType::from(f.field_type) == FieldType::UpdatedAt) let old_updated_at = DateCellData::from(cell).timestamp.unwrap();
.unwrap();
let cell = row.cells.cell_for_field_id(&updated_at_field.id).unwrap(); test
let updated_at_timestamp = DateCellData::from(cell).timestamp.unwrap(); .run_script(UpdateTextCell {
row_id: row.id.clone(),
assert!( content: "test".to_string(),
updated_at_timestamp >= before_create_timestamp })
&& updated_at_timestamp <= after_create_timestamp, .await;
"timestamp: {}, before: {}, after: {}",
updated_at_timestamp, // Get the updated time of the row.
before_create_timestamp, let row = test.get_rows().await.remove(0);
after_create_timestamp let updated_at_field = test.get_first_field(FieldType::UpdatedAt);
); let cell = row.cells.cell_for_field_id(&updated_at_field.id).unwrap();
let new_updated_at = DateCellData::from(cell).timestamp.unwrap();
assert!(old_updated_at < new_updated_at);
} }

View File

@ -1,8 +1,12 @@
use crate::database::database_editor::DatabaseEditorTest; use crate::database::database_editor::DatabaseEditorTest;
use flowy_database2::entities::CreateRowParams; use collab_database::database::gen_row_id;
use collab_database::rows::RowId;
use lib_infra::util::timestamp;
pub enum RowScript { pub enum RowScript {
CreateEmptyRow, CreateEmptyRow,
UpdateTextCell { row_id: RowId, content: String },
AssertRowCount(usize), AssertRowCount(usize),
} }
@ -25,18 +29,25 @@ impl DatabaseRowTest {
pub async fn run_script(&mut self, script: RowScript) { pub async fn run_script(&mut self, script: RowScript) {
match script { match script {
RowScript::CreateEmptyRow => { RowScript::CreateEmptyRow => {
let params = CreateRowParams { let params = collab_database::rows::CreateRowParams {
view_id: self.view_id.clone(), id: gen_row_id(),
start_row_id: None, timestamp: timestamp(),
group_id: None, ..Default::default()
cell_data_by_field_id: None,
}; };
let row_order = self.editor.create_row(params).await.unwrap().unwrap(); let row_order = self
.editor
.create_row(&self.view_id, None, params)
.await
.unwrap()
.unwrap();
self self
.row_by_row_id .row_by_row_id
.insert(row_order.id.to_string(), row_order.into()); .insert(row_order.id.to_string(), row_order.into());
self.rows = self.get_rows().await; self.rows = self.get_rows().await;
}, },
RowScript::UpdateTextCell { row_id, content } => {
self.update_text_cell(row_id, &content).await.unwrap();
},
RowScript::AssertRowCount(expected_row_count) => { RowScript::AssertRowCount(expected_row_count) => {
assert_eq!(expected_row_count, self.rows.len()); assert_eq!(expected_row_count, self.rows.len());
}, },

View File

@ -1,6 +1,7 @@
use collab_database::database::gen_row_id;
use collab_database::fields::Field; use collab_database::fields::Field;
use collab_database::rows::RowId; use collab_database::rows::{CreateRowParams, RowId};
use flowy_database2::entities::{CreateRowParams, FieldType, GroupPB, RowPB}; use flowy_database2::entities::{FieldType, GroupPB, RowPB};
use flowy_database2::services::cell::{ use flowy_database2::services::cell::{
delete_select_option_cell, insert_select_option_cell, insert_url_cell, delete_select_option_cell, insert_select_option_cell, insert_url_cell,
}; };
@ -8,6 +9,7 @@ use flowy_database2::services::field::{
edit_single_select_type_option, SelectOption, SelectTypeOptionSharedAction, edit_single_select_type_option, SelectOption, SelectTypeOptionSharedAction,
SingleSelectTypeOption, SingleSelectTypeOption,
}; };
use lib_infra::util::timestamp;
use crate::database::database_editor::DatabaseEditorTest; use crate::database::database_editor::DatabaseEditorTest;
@ -126,12 +128,15 @@ impl DatabaseGroupTest {
GroupScript::CreateRow { group_index } => { GroupScript::CreateRow { group_index } => {
let group = self.group_at_index(group_index).await; let group = self.group_at_index(group_index).await;
let params = CreateRowParams { let params = CreateRowParams {
view_id: self.view_id.clone(), id: gen_row_id(),
start_row_id: None, timestamp: timestamp(),
group_id: Some(group.group_id.clone()), ..Default::default()
cell_data_by_field_id: None,
}; };
let _ = self.editor.create_row(params).await.unwrap(); let _ = self
.editor
.create_row(&self.view_id, Some(group.group_id.clone()), params)
.await
.unwrap();
}, },
GroupScript::DeleteRow { GroupScript::DeleteRow {
group_index, group_index,

View File

@ -264,14 +264,7 @@ pub fn make_test_grid() -> DatabaseData {
database_id: gen_database_id(), database_id: gen_database_id(),
name: "".to_string(), name: "".to_string(),
layout: DatabaseLayout::Grid, layout: DatabaseLayout::Grid,
layout_settings: Default::default(), ..Default::default()
filters: vec![],
group_settings: vec![],
sorts: vec![],
row_orders: vec![],
field_orders: vec![],
created_at: 0,
modified_at: 0,
}; };
DatabaseData { view, fields, rows } DatabaseData { view, fields, rows }