mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: add tauri database group test (#1924)
* chore: add tauri database group test * chore: add more tests * chore: enable run all tests * chore: rename test folder
This commit is contained in:
parent
8e22ef2230
commit
7e7cee4bf4
@ -1,9 +1,9 @@
|
|||||||
|
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/grid_entities.pb.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/group.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/group.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart';
|
||||||
|
|
||||||
@ -60,6 +60,6 @@ class DatabaseBackendService {
|
|||||||
|
|
||||||
Future<Either<RepeatedGroupPB, FlowyError>> loadGroups() {
|
Future<Either<RepeatedGroupPB, FlowyError>> loadGroups() {
|
||||||
final payload = DatabaseViewIdPB(value: viewId);
|
final payload = DatabaseViewIdPB(value: viewId);
|
||||||
return DatabaseEventGetGroup(payload).send();
|
return DatabaseEventGetGroups(payload).send();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/grid_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
|
|
||||||
import '../grid/presentation/widgets/filter/filter_info.dart';
|
import '../grid/presentation/widgets/filter/filter_info.dart';
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/grid_entities.pb.dart';
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
part 'field_service.freezed.dart';
|
part 'field_service.freezed.dart';
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
@ -6,7 +7,6 @@ import 'package:appflowy_backend/protobuf/flowy-database/checkbox_filter.pbserve
|
|||||||
import 'package:appflowy_backend/protobuf/flowy-database/checklist_filter.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/checklist_filter.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/date_filter.pbserver.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/date_filter.pbserver.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/grid_entities.pb.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/number_filter.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/number_filter.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/select_option_filter.pbserver.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/select_option_filter.pbserver.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pb.dart';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/grid_entities.pb.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/group_changeset.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/group_changeset.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart';
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/grid_entities.pb.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/group.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/group.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pb.dart';
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
|
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/grid_entities.pb.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database/sort_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database/sort_entities.pb.dart';
|
||||||
|
|
||||||
|
@ -68,9 +68,8 @@ class GroupController {
|
|||||||
|
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
group.rows[index] = updatedRow;
|
group.rows[index] = updatedRow;
|
||||||
|
delegate.updateRow(group, updatedRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate.updateRow(group, updatedRow);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
@ -78,29 +77,6 @@ class GroupController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupChangesetPB _transformChangeset(GroupChangesetPB changeset) {
|
|
||||||
// final insertedRows = changeset.insertedRows
|
|
||||||
// .where(
|
|
||||||
// (delete) => !changeset.deletedRows.contains(delete.row.id),
|
|
||||||
// )
|
|
||||||
// .toList();
|
|
||||||
|
|
||||||
// final deletedRows = changeset.deletedRows
|
|
||||||
// .where((deletedRowId) =>
|
|
||||||
// changeset.insertedRows
|
|
||||||
// .indexWhere((insert) => insert.row.id == deletedRowId) ==
|
|
||||||
// -1)
|
|
||||||
// .toList();
|
|
||||||
|
|
||||||
// return changeset.rebuild((rebuildChangeset) {
|
|
||||||
// rebuildChangeset.insertedRows.clear();
|
|
||||||
// rebuildChangeset.insertedRows.addAll(insertedRows);
|
|
||||||
|
|
||||||
// rebuildChangeset.deletedRows.clear();
|
|
||||||
// rebuildChangeset.deletedRows.addAll(deletedRows);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
_listener.stop();
|
_listener.stop();
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import { SignUpPage } from './views/SignUpPage';
|
|||||||
import { ConfirmAccountPage } from './views/ConfirmAccountPage';
|
import { ConfirmAccountPage } from './views/ConfirmAccountPage';
|
||||||
import { ErrorHandlerPage } from './components/error/ErrorHandlerPage';
|
import { ErrorHandlerPage } from './components/error/ErrorHandlerPage';
|
||||||
import initializeI18n from './stores/i18n/initializeI18n';
|
import initializeI18n from './stores/i18n/initializeI18n';
|
||||||
import { TestAPI } from './components/TestApiButton/TestAPI';
|
import { TestAPI } from './components/tests/TestAPI';
|
||||||
import { GetStarted } from './components/auth/GetStarted/GetStarted';
|
import { GetStarted } from './components/auth/GetStarted/GetStarted';
|
||||||
|
|
||||||
initializeI18n();
|
initializeI18n();
|
||||||
|
@ -1,334 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
FieldType,
|
|
||||||
NumberFormat,
|
|
||||||
NumberTypeOptionPB,
|
|
||||||
SelectOptionCellDataPB,
|
|
||||||
SingleSelectTypeOptionPB,
|
|
||||||
ViewLayoutTypePB,
|
|
||||||
} from '../../../services/backend';
|
|
||||||
import { Log } from '../../utils/log';
|
|
||||||
import {
|
|
||||||
assertFieldName,
|
|
||||||
assertNumberOfFields,
|
|
||||||
assertNumberOfRows,
|
|
||||||
assertTextCell,
|
|
||||||
createTestDatabaseView,
|
|
||||||
editTextCell,
|
|
||||||
findFirstFieldInfoWithFieldType,
|
|
||||||
makeMultiSelectCellController,
|
|
||||||
makeSingleSelectCellController,
|
|
||||||
makeTextCellController,
|
|
||||||
openTestDatabase,
|
|
||||||
} from './DatabaseTestHelper';
|
|
||||||
import {
|
|
||||||
SelectOptionBackendService,
|
|
||||||
SelectOptionCellBackendService,
|
|
||||||
} from '../../stores/effects/database/cell/select_option_bd_svc';
|
|
||||||
import { TypeOptionController } from '../../stores/effects/database/field/type_option/type_option_controller';
|
|
||||||
import { None, Some } from 'ts-results';
|
|
||||||
import { RowBackendService } from '../../stores/effects/database/row/row_bd_svc';
|
|
||||||
import {
|
|
||||||
makeNumberTypeOptionContext,
|
|
||||||
makeSingleSelectTypeOptionContext,
|
|
||||||
} from '../../stores/effects/database/field/type_option/type_option_context';
|
|
||||||
|
|
||||||
export const TestCreateGrid = () => {
|
|
||||||
async function createBuildInGrid() {
|
|
||||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
|
||||||
const databaseController = await openTestDatabase(view.id);
|
|
||||||
databaseController.subscribe({
|
|
||||||
onViewChanged: (databasePB) => {
|
|
||||||
Log.debug('Did receive database:' + databasePB);
|
|
||||||
},
|
|
||||||
// onRowsChanged: async (rows) => {
|
|
||||||
// if (rows.length !== 3) {
|
|
||||||
// throw Error('Expected number of rows is 3, but receive ' + rows.length);
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
onFieldsChanged: (fields) => {
|
|
||||||
if (fields.length !== 3) {
|
|
||||||
throw Error('Expected number of fields is 3, but receive ' + fields.length);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await databaseController.open().then((result) => result.unwrap());
|
|
||||||
await databaseController.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return TestButton('Test create build-in grid', createBuildInGrid);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TestEditCell = () => {
|
|
||||||
async function testGridRow() {
|
|
||||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
|
||||||
const databaseController = await openTestDatabase(view.id);
|
|
||||||
await databaseController.open().then((result) => result.unwrap());
|
|
||||||
|
|
||||||
for (const [index, row] of databaseController.databaseViewCache.rowInfos.entries()) {
|
|
||||||
const cellContent = index.toString();
|
|
||||||
const fieldInfo = findFirstFieldInfoWithFieldType(row, FieldType.RichText).unwrap();
|
|
||||||
await editTextCell(fieldInfo.field.id, row, databaseController, cellContent);
|
|
||||||
await assertTextCell(fieldInfo.field.id, row, databaseController, cellContent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return TestButton('Test editing cell', testGridRow);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TestCreateRow = () => {
|
|
||||||
async function testCreateRow() {
|
|
||||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
|
||||||
const databaseController = await openTestDatabase(view.id);
|
|
||||||
await databaseController.open().then((result) => result.unwrap());
|
|
||||||
await assertNumberOfRows(view.id, 3);
|
|
||||||
|
|
||||||
// Create a row from a DatabaseController or create using the RowBackendService
|
|
||||||
await databaseController.createRow();
|
|
||||||
await assertNumberOfRows(view.id, 4);
|
|
||||||
await databaseController.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return TestButton('Test create row', testCreateRow);
|
|
||||||
};
|
|
||||||
export const TestDeleteRow = () => {
|
|
||||||
async function testDeleteRow() {
|
|
||||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
|
||||||
const databaseController = await openTestDatabase(view.id);
|
|
||||||
await databaseController.open().then((result) => result.unwrap());
|
|
||||||
|
|
||||||
const rows = databaseController.databaseViewCache.rowInfos;
|
|
||||||
const svc = new RowBackendService(view.id);
|
|
||||||
await svc.deleteRow(rows[0].row.id);
|
|
||||||
await assertNumberOfRows(view.id, 2);
|
|
||||||
|
|
||||||
// Wait the databaseViewCache get the change notification and
|
|
||||||
// update the rows.
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
||||||
if (databaseController.databaseViewCache.rowInfos.length !== 2) {
|
|
||||||
throw Error('The number of rows is not match');
|
|
||||||
}
|
|
||||||
await databaseController.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return TestButton('Test delete row', testDeleteRow);
|
|
||||||
};
|
|
||||||
export const TestCreateSelectOptionInCell = () => {
|
|
||||||
async function testCreateOptionInCell() {
|
|
||||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
|
||||||
const databaseController = await openTestDatabase(view.id);
|
|
||||||
await databaseController.open().then((result) => result.unwrap());
|
|
||||||
for (const [index, row] of databaseController.databaseViewCache.rowInfos.entries()) {
|
|
||||||
if (index === 0) {
|
|
||||||
const fieldInfo = findFirstFieldInfoWithFieldType(row, FieldType.SingleSelect).unwrap();
|
|
||||||
const cellController = await makeSingleSelectCellController(fieldInfo.field.id, row, databaseController).then(
|
|
||||||
(result) => result.unwrap()
|
|
||||||
);
|
|
||||||
await cellController.subscribeChanged({
|
|
||||||
onCellChanged: (value) => {
|
|
||||||
if (value.some) {
|
|
||||||
const option: SelectOptionCellDataPB = value.unwrap();
|
|
||||||
console.log(option);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const backendSvc = new SelectOptionCellBackendService(cellController.cellIdentifier);
|
|
||||||
await backendSvc.createOption({ name: 'option' + index });
|
|
||||||
await cellController.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await databaseController.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return TestButton('Test create a select option in cell', testCreateOptionInCell);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TestGetSingleSelectFieldData = () => {
|
|
||||||
async function testGetSingleSelectFieldData() {
|
|
||||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
|
||||||
const databaseController = await openTestDatabase(view.id);
|
|
||||||
await databaseController.open().then((result) => result.unwrap());
|
|
||||||
|
|
||||||
// Find the single select column
|
|
||||||
const singleSelect = databaseController.fieldController.fieldInfos.find(
|
|
||||||
(fieldInfo) => fieldInfo.field.field_type === FieldType.SingleSelect
|
|
||||||
)!;
|
|
||||||
const typeOptionController = new TypeOptionController(view.id, Some(singleSelect));
|
|
||||||
const singleSelectTypeOptionContext = makeSingleSelectTypeOptionContext(typeOptionController);
|
|
||||||
|
|
||||||
// Create options
|
|
||||||
const singleSelectTypeOptionPB: SingleSelectTypeOptionPB = await singleSelectTypeOptionContext
|
|
||||||
.getTypeOption()
|
|
||||||
.then((result) => result.unwrap());
|
|
||||||
const backendSvc = new SelectOptionBackendService(view.id, singleSelect.field.id);
|
|
||||||
const option1 = await backendSvc.createOption({ name: 'Task 1' }).then((result) => result.unwrap());
|
|
||||||
singleSelectTypeOptionPB.options.splice(0, 0, option1);
|
|
||||||
const option2 = await backendSvc.createOption({ name: 'Task 2' }).then((result) => result.unwrap());
|
|
||||||
singleSelectTypeOptionPB.options.splice(0, 0, option2);
|
|
||||||
const option3 = await backendSvc.createOption({ name: 'Task 3' }).then((result) => result.unwrap());
|
|
||||||
singleSelectTypeOptionPB.options.splice(0, 0, option3);
|
|
||||||
await singleSelectTypeOptionContext.setTypeOption(singleSelectTypeOptionPB);
|
|
||||||
|
|
||||||
// Read options
|
|
||||||
const options = singleSelectTypeOptionPB.options;
|
|
||||||
console.log(options);
|
|
||||||
|
|
||||||
await databaseController.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return TestButton('Test get single-select column data', testGetSingleSelectFieldData);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TestSwitchFromSingleSelectToNumber = () => {
|
|
||||||
async function testSwitchFromSingleSelectToNumber() {
|
|
||||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
|
||||||
const databaseController = await openTestDatabase(view.id);
|
|
||||||
await databaseController.open().then((result) => result.unwrap());
|
|
||||||
|
|
||||||
// Find the single select column
|
|
||||||
const singleSelect = databaseController.fieldController.fieldInfos.find(
|
|
||||||
(fieldInfo) => fieldInfo.field.field_type === FieldType.SingleSelect
|
|
||||||
)!;
|
|
||||||
const typeOptionController = new TypeOptionController(view.id, Some(singleSelect));
|
|
||||||
await typeOptionController.switchToField(FieldType.Number);
|
|
||||||
|
|
||||||
// Check the number type option
|
|
||||||
const numberTypeOptionContext = makeNumberTypeOptionContext(typeOptionController);
|
|
||||||
const numberTypeOption: NumberTypeOptionPB = await numberTypeOptionContext
|
|
||||||
.getTypeOption()
|
|
||||||
.then((result) => result.unwrap());
|
|
||||||
const format: NumberFormat = numberTypeOption.format;
|
|
||||||
if (format !== NumberFormat.Num) {
|
|
||||||
throw Error('The default format should be number');
|
|
||||||
}
|
|
||||||
|
|
||||||
await databaseController.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return TestButton('Test switch from single-select to number column', testSwitchFromSingleSelectToNumber);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TestSwitchFromMultiSelectToText = () => {
|
|
||||||
async function testSwitchFromMultiSelectToRichText() {
|
|
||||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
|
||||||
const databaseController = await openTestDatabase(view.id);
|
|
||||||
await databaseController.open().then((result) => result.unwrap());
|
|
||||||
|
|
||||||
// Create multi-select field
|
|
||||||
const typeOptionController = new TypeOptionController(view.id, None, FieldType.MultiSelect);
|
|
||||||
await typeOptionController.initialize();
|
|
||||||
|
|
||||||
// Insert options to first row
|
|
||||||
const row = databaseController.databaseViewCache.rowInfos[0];
|
|
||||||
const multiSelectField = typeOptionController.getFieldInfo();
|
|
||||||
// const multiSelectField = findFirstFieldInfoWithFieldType(row, FieldType.MultiSelect).unwrap();
|
|
||||||
const selectOptionCellController = await makeMultiSelectCellController(
|
|
||||||
multiSelectField.field.id,
|
|
||||||
row,
|
|
||||||
databaseController
|
|
||||||
).then((result) => result.unwrap());
|
|
||||||
const backendSvc = new SelectOptionCellBackendService(selectOptionCellController.cellIdentifier);
|
|
||||||
await backendSvc.createOption({ name: 'A' });
|
|
||||||
await backendSvc.createOption({ name: 'B' });
|
|
||||||
await backendSvc.createOption({ name: 'C' });
|
|
||||||
|
|
||||||
const selectOptionCellData = await selectOptionCellController.getCellData().then((result) => result.unwrap());
|
|
||||||
if (selectOptionCellData.options.length !== 3) {
|
|
||||||
throw Error('The options should equal to 3');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectOptionCellData.select_options.length !== 3) {
|
|
||||||
throw Error('The selected options should equal to 3');
|
|
||||||
}
|
|
||||||
await selectOptionCellController.dispose();
|
|
||||||
|
|
||||||
// Switch to RichText field type
|
|
||||||
await typeOptionController.switchToField(FieldType.RichText).then((result) => result.unwrap());
|
|
||||||
if (typeOptionController.fieldType !== FieldType.RichText) {
|
|
||||||
throw Error('The field type should be text');
|
|
||||||
}
|
|
||||||
|
|
||||||
const textCellController = await makeTextCellController(multiSelectField.field.id, row, databaseController).then(
|
|
||||||
(result) => result.unwrap()
|
|
||||||
);
|
|
||||||
const cellContent = await textCellController.getCellData();
|
|
||||||
if (cellContent.unwrap() !== 'A,B,C') {
|
|
||||||
throw Error('The cell content should be A,B,C, but receive: ' + cellContent.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
await databaseController.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return TestButton('Test switch from multi-select to text column', testSwitchFromMultiSelectToRichText);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TestEditField = () => {
|
|
||||||
async function testEditField() {
|
|
||||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
|
||||||
const databaseController = await openTestDatabase(view.id);
|
|
||||||
await databaseController.open().then((result) => result.unwrap());
|
|
||||||
const fieldInfos = databaseController.fieldController.fieldInfos;
|
|
||||||
|
|
||||||
// Modify the name of the field
|
|
||||||
const firstFieldInfo = fieldInfos[0];
|
|
||||||
const controller = new TypeOptionController(view.id, Some(firstFieldInfo));
|
|
||||||
await controller.initialize();
|
|
||||||
const newName = 'hello world';
|
|
||||||
await controller.setFieldName(newName);
|
|
||||||
|
|
||||||
await assertFieldName(view.id, firstFieldInfo.field.id, firstFieldInfo.field.field_type, newName);
|
|
||||||
await databaseController.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return TestButton('Test edit the column name', testEditField);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TestCreateNewField = () => {
|
|
||||||
async function testCreateNewField() {
|
|
||||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
|
||||||
const databaseController = await openTestDatabase(view.id);
|
|
||||||
await databaseController.open().then((result) => result.unwrap());
|
|
||||||
await assertNumberOfFields(view.id, 3);
|
|
||||||
|
|
||||||
// Modify the name of the field
|
|
||||||
const controller = new TypeOptionController(view.id, None);
|
|
||||||
await controller.initialize();
|
|
||||||
await assertNumberOfFields(view.id, 4);
|
|
||||||
await databaseController.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return TestButton('Test create a new column', testCreateNewField);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TestDeleteField = () => {
|
|
||||||
async function testDeleteField() {
|
|
||||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
|
||||||
const databaseController = await openTestDatabase(view.id);
|
|
||||||
await databaseController.open().then((result) => result.unwrap());
|
|
||||||
|
|
||||||
// Modify the name of the field.
|
|
||||||
// The fieldInfos[0] is the primary field by default, we can't delete it.
|
|
||||||
// So let choose the second fieldInfo.
|
|
||||||
const fieldInfo = databaseController.fieldController.fieldInfos[1];
|
|
||||||
const controller = new TypeOptionController(view.id, Some(fieldInfo));
|
|
||||||
await controller.initialize();
|
|
||||||
await assertNumberOfFields(view.id, 3);
|
|
||||||
await controller.deleteField();
|
|
||||||
await assertNumberOfFields(view.id, 2);
|
|
||||||
await databaseController.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return TestButton('Test delete a new column', testDeleteField);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TestButton = (title: string, onClick: () => void) => {
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<div>
|
|
||||||
<button className='rounded-md bg-gray-300 p-4' type='button' onClick={() => onClick()}>
|
|
||||||
{title}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,4 +1,10 @@
|
|||||||
import { FieldType, ViewLayoutTypePB, ViewPB, WorkspaceSettingPB } from '../../../services/backend';
|
import {
|
||||||
|
FieldType,
|
||||||
|
SingleSelectTypeOptionPB,
|
||||||
|
ViewLayoutTypePB,
|
||||||
|
ViewPB,
|
||||||
|
WorkspaceSettingPB,
|
||||||
|
} from '../../../services/backend';
|
||||||
import { FolderEventReadCurrentWorkspace } from '../../../services/backend/events/flowy-folder';
|
import { FolderEventReadCurrentWorkspace } from '../../../services/backend/events/flowy-folder';
|
||||||
import { AppBackendService } from '../../stores/effects/folder/app/app_bd_svc';
|
import { AppBackendService } from '../../stores/effects/folder/app/app_bd_svc';
|
||||||
import { DatabaseController } from '../../stores/effects/database/database_controller';
|
import { DatabaseController } from '../../stores/effects/database/database_controller';
|
||||||
@ -14,6 +20,10 @@ import {
|
|||||||
import { None, Option, Some } from 'ts-results';
|
import { None, Option, Some } from 'ts-results';
|
||||||
import { TypeOptionBackendService } from '../../stores/effects/database/field/type_option/type_option_bd_svc';
|
import { TypeOptionBackendService } from '../../stores/effects/database/field/type_option/type_option_bd_svc';
|
||||||
import { DatabaseBackendService } from '../../stores/effects/database/database_bd_svc';
|
import { DatabaseBackendService } from '../../stores/effects/database/database_bd_svc';
|
||||||
|
import { FieldInfo } from '../../stores/effects/database/field/field_controller';
|
||||||
|
import { TypeOptionController } from '../../stores/effects/database/field/type_option/type_option_controller';
|
||||||
|
import { makeSingleSelectTypeOptionContext } from '../../stores/effects/database/field/type_option/type_option_context';
|
||||||
|
import { SelectOptionBackendService } from '../../stores/effects/database/cell/select_option_bd_svc';
|
||||||
|
|
||||||
// Create a database view for specific layout type
|
// Create a database view for specific layout type
|
||||||
// Do not use it production code. Just for testing
|
// Do not use it production code. Just for testing
|
||||||
@ -168,3 +178,36 @@ export async function assertNumberOfRows(viewId: string, expected: number) {
|
|||||||
throw Error('Expect number of rows:' + expected + 'but receive:' + databasePB.rows.length);
|
throw Error('Expect number of rows:' + expected + 'but receive:' + databasePB.rows.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function assertNumberOfRowsInGroup(viewId: string, groupId: string, expected: number) {
|
||||||
|
const svc = new DatabaseBackendService(viewId);
|
||||||
|
await svc.openDatabase();
|
||||||
|
|
||||||
|
const group = await svc.getGroup(groupId).then((result) => result.unwrap());
|
||||||
|
if (group.rows.length !== expected) {
|
||||||
|
throw Error('Expect number of rows in group:' + expected + 'but receive:' + group.rows.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createSingleSelectOptions(viewId: string, fieldInfo: FieldInfo, optionNames: string[]) {
|
||||||
|
assert(fieldInfo.field.field_type === FieldType.SingleSelect, 'Only work on single select');
|
||||||
|
const typeOptionController = new TypeOptionController(viewId, Some(fieldInfo));
|
||||||
|
const singleSelectTypeOptionContext = makeSingleSelectTypeOptionContext(typeOptionController);
|
||||||
|
const singleSelectTypeOptionPB: SingleSelectTypeOptionPB = await singleSelectTypeOptionContext
|
||||||
|
.getTypeOption()
|
||||||
|
.then((result) => result.unwrap());
|
||||||
|
|
||||||
|
const backendSvc = new SelectOptionBackendService(viewId, fieldInfo.field.id);
|
||||||
|
for (const optionName of optionNames) {
|
||||||
|
const option = await backendSvc.createOption({ name: optionName }).then((result) => result.unwrap());
|
||||||
|
singleSelectTypeOptionPB.options.splice(0, 0, option);
|
||||||
|
}
|
||||||
|
await singleSelectTypeOptionContext.setTypeOption(singleSelectTypeOptionPB);
|
||||||
|
return singleSelectTypeOptionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assert(condition: boolean, msg?: string) {
|
||||||
|
if (!condition) {
|
||||||
|
throw Error(msg);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
|
RunAllGridTests,
|
||||||
TestCreateGrid,
|
TestCreateGrid,
|
||||||
TestCreateNewField,
|
TestCreateNewField,
|
||||||
TestCreateRow,
|
TestCreateRow,
|
||||||
@ -12,12 +13,21 @@ import {
|
|||||||
TestSwitchFromMultiSelectToText,
|
TestSwitchFromMultiSelectToText,
|
||||||
TestSwitchFromSingleSelectToNumber,
|
TestSwitchFromSingleSelectToNumber,
|
||||||
} from './TestGrid';
|
} from './TestGrid';
|
||||||
|
import {
|
||||||
|
TestCreateKanbanBoard,
|
||||||
|
TestCreateKanbanBoardColumn,
|
||||||
|
TestCreateKanbanBoardRowInNoStatusGroup,
|
||||||
|
TestAllKanbanTests,
|
||||||
|
TestMoveKanbanBoardColumn,
|
||||||
|
TestMoveKanbanBoardRow,
|
||||||
|
} from './TestGroup';
|
||||||
|
|
||||||
export const TestAPI = () => {
|
export const TestAPI = () => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ul className='m-6, space-y-2'>
|
<ul className='m-6, space-y-2'>
|
||||||
{/*<TestApiButton></TestApiButton>*/}
|
{/*<tests></tests>*/}
|
||||||
|
<RunAllGridTests></RunAllGridTests>
|
||||||
<TestCreateGrid></TestCreateGrid>
|
<TestCreateGrid></TestCreateGrid>
|
||||||
<TestCreateRow></TestCreateRow>
|
<TestCreateRow></TestCreateRow>
|
||||||
<TestDeleteRow></TestDeleteRow>
|
<TestDeleteRow></TestDeleteRow>
|
||||||
@ -29,6 +39,13 @@ export const TestAPI = () => {
|
|||||||
<TestDeleteField></TestDeleteField>
|
<TestDeleteField></TestDeleteField>
|
||||||
<TestSwitchFromSingleSelectToNumber></TestSwitchFromSingleSelectToNumber>
|
<TestSwitchFromSingleSelectToNumber></TestSwitchFromSingleSelectToNumber>
|
||||||
<TestSwitchFromMultiSelectToText></TestSwitchFromMultiSelectToText>
|
<TestSwitchFromMultiSelectToText></TestSwitchFromMultiSelectToText>
|
||||||
|
{/*kanban board */}
|
||||||
|
<TestAllKanbanTests></TestAllKanbanTests>
|
||||||
|
<TestCreateKanbanBoard></TestCreateKanbanBoard>
|
||||||
|
<TestCreateKanbanBoardRowInNoStatusGroup></TestCreateKanbanBoardRowInNoStatusGroup>
|
||||||
|
<TestMoveKanbanBoardRow></TestMoveKanbanBoardRow>
|
||||||
|
<TestMoveKanbanBoardColumn></TestMoveKanbanBoardColumn>
|
||||||
|
<TestCreateKanbanBoardColumn></TestCreateKanbanBoardColumn>
|
||||||
</ul>
|
</ul>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
@ -0,0 +1,349 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
FieldType,
|
||||||
|
NumberFormat,
|
||||||
|
NumberTypeOptionPB,
|
||||||
|
SelectOptionCellDataPB,
|
||||||
|
ViewLayoutTypePB,
|
||||||
|
} from '../../../services/backend';
|
||||||
|
import { Log } from '../../utils/log';
|
||||||
|
import {
|
||||||
|
assertFieldName,
|
||||||
|
assertNumberOfFields,
|
||||||
|
assertNumberOfRows,
|
||||||
|
assertTextCell,
|
||||||
|
createSingleSelectOptions,
|
||||||
|
createTestDatabaseView,
|
||||||
|
editTextCell,
|
||||||
|
findFirstFieldInfoWithFieldType,
|
||||||
|
makeMultiSelectCellController,
|
||||||
|
makeSingleSelectCellController,
|
||||||
|
makeTextCellController,
|
||||||
|
openTestDatabase,
|
||||||
|
} from './DatabaseTestHelper';
|
||||||
|
import { SelectOptionCellBackendService } from '../../stores/effects/database/cell/select_option_bd_svc';
|
||||||
|
import { TypeOptionController } from '../../stores/effects/database/field/type_option/type_option_controller';
|
||||||
|
import { None, Some } from 'ts-results';
|
||||||
|
import { RowBackendService } from '../../stores/effects/database/row/row_bd_svc';
|
||||||
|
import { makeNumberTypeOptionContext } from '../../stores/effects/database/field/type_option/type_option_context';
|
||||||
|
|
||||||
|
export const RunAllGridTests = () => {
|
||||||
|
async function run() {
|
||||||
|
await createBuildInGrid();
|
||||||
|
await testEditGridRow();
|
||||||
|
await testCreateRow();
|
||||||
|
await testDeleteRow();
|
||||||
|
await testCreateOptionInCell();
|
||||||
|
await testGetSingleSelectFieldData();
|
||||||
|
await testSwitchFromSingleSelectToNumber();
|
||||||
|
await testSwitchFromMultiSelectToRichText();
|
||||||
|
await testEditField();
|
||||||
|
await testCreateNewField();
|
||||||
|
await testDeleteField();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<div>
|
||||||
|
<button className='rounded-md bg-red-400 p-4' type='button' onClick={() => run()}>
|
||||||
|
Run all grid tests
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function createBuildInGrid() {
|
||||||
|
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||||
|
const databaseController = await openTestDatabase(view.id);
|
||||||
|
databaseController.subscribe({
|
||||||
|
onViewChanged: (databasePB) => {
|
||||||
|
Log.debug('Did receive database:' + databasePB);
|
||||||
|
},
|
||||||
|
// onRowsChanged: async (rows) => {
|
||||||
|
// if (rows.length !== 3) {
|
||||||
|
// throw Error('Expected number of rows is 3, but receive ' + rows.length);
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
onFieldsChanged: (fields) => {
|
||||||
|
if (fields.length !== 3) {
|
||||||
|
throw Error('Expected number of fields is 3, but receive ' + fields.length);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await databaseController.open().then((result) => result.unwrap());
|
||||||
|
await databaseController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testEditGridRow() {
|
||||||
|
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||||
|
const databaseController = await openTestDatabase(view.id);
|
||||||
|
await databaseController.open().then((result) => result.unwrap());
|
||||||
|
|
||||||
|
for (const [index, row] of databaseController.databaseViewCache.rowInfos.entries()) {
|
||||||
|
const cellContent = index.toString();
|
||||||
|
const fieldInfo = findFirstFieldInfoWithFieldType(row, FieldType.RichText).unwrap();
|
||||||
|
await editTextCell(fieldInfo.field.id, row, databaseController, cellContent);
|
||||||
|
await assertTextCell(fieldInfo.field.id, row, databaseController, cellContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testCreateRow() {
|
||||||
|
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||||
|
const databaseController = await openTestDatabase(view.id);
|
||||||
|
await databaseController.open().then((result) => result.unwrap());
|
||||||
|
await assertNumberOfRows(view.id, 3);
|
||||||
|
|
||||||
|
// Create a row from a DatabaseController or create using the RowBackendService
|
||||||
|
await databaseController.createRow();
|
||||||
|
await assertNumberOfRows(view.id, 4);
|
||||||
|
await databaseController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testDeleteRow() {
|
||||||
|
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||||
|
const databaseController = await openTestDatabase(view.id);
|
||||||
|
await databaseController.open().then((result) => result.unwrap());
|
||||||
|
|
||||||
|
const rows = databaseController.databaseViewCache.rowInfos;
|
||||||
|
const svc = new RowBackendService(view.id);
|
||||||
|
await svc.deleteRow(rows[0].row.id);
|
||||||
|
await assertNumberOfRows(view.id, 2);
|
||||||
|
|
||||||
|
// Wait the databaseViewCache get the change notification and
|
||||||
|
// update the rows.
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||||
|
if (databaseController.databaseViewCache.rowInfos.length !== 2) {
|
||||||
|
throw Error('The number of rows is not match');
|
||||||
|
}
|
||||||
|
await databaseController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testCreateOptionInCell() {
|
||||||
|
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||||
|
const databaseController = await openTestDatabase(view.id);
|
||||||
|
await databaseController.open().then((result) => result.unwrap());
|
||||||
|
for (const [index, row] of databaseController.databaseViewCache.rowInfos.entries()) {
|
||||||
|
if (index === 0) {
|
||||||
|
const fieldInfo = findFirstFieldInfoWithFieldType(row, FieldType.SingleSelect).unwrap();
|
||||||
|
const cellController = await makeSingleSelectCellController(fieldInfo.field.id, row, databaseController).then(
|
||||||
|
(result) => result.unwrap()
|
||||||
|
);
|
||||||
|
await cellController.subscribeChanged({
|
||||||
|
onCellChanged: (value) => {
|
||||||
|
if (value.some) {
|
||||||
|
const option: SelectOptionCellDataPB = value.unwrap();
|
||||||
|
console.log(option);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const backendSvc = new SelectOptionCellBackendService(cellController.cellIdentifier);
|
||||||
|
await backendSvc.createOption({ name: 'option' + index });
|
||||||
|
await cellController.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await databaseController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testGetSingleSelectFieldData() {
|
||||||
|
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||||
|
const databaseController = await openTestDatabase(view.id);
|
||||||
|
await databaseController.open().then((result) => result.unwrap());
|
||||||
|
|
||||||
|
// Find the single select column
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const singleSelect = databaseController.fieldController.fieldInfos.find(
|
||||||
|
(fieldInfo) => fieldInfo.field.field_type === FieldType.SingleSelect
|
||||||
|
)!;
|
||||||
|
|
||||||
|
// Create options
|
||||||
|
const singleSelectTypeOptionContext = await createSingleSelectOptions(view.id, singleSelect, [
|
||||||
|
'Task 1',
|
||||||
|
'Task 2',
|
||||||
|
'Task 3',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Read options
|
||||||
|
const options = await singleSelectTypeOptionContext.getTypeOption().then((result) => result.unwrap());
|
||||||
|
console.log(options);
|
||||||
|
|
||||||
|
await databaseController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testSwitchFromSingleSelectToNumber() {
|
||||||
|
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||||
|
const databaseController = await openTestDatabase(view.id);
|
||||||
|
await databaseController.open().then((result) => result.unwrap());
|
||||||
|
|
||||||
|
// Find the single select column
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const singleSelect = databaseController.fieldController.fieldInfos.find(
|
||||||
|
(fieldInfo) => fieldInfo.field.field_type === FieldType.SingleSelect
|
||||||
|
)!;
|
||||||
|
const typeOptionController = new TypeOptionController(view.id, Some(singleSelect));
|
||||||
|
await typeOptionController.switchToField(FieldType.Number);
|
||||||
|
|
||||||
|
// Check the number type option
|
||||||
|
const numberTypeOptionContext = makeNumberTypeOptionContext(typeOptionController);
|
||||||
|
const numberTypeOption: NumberTypeOptionPB = await numberTypeOptionContext
|
||||||
|
.getTypeOption()
|
||||||
|
.then((result) => result.unwrap());
|
||||||
|
const format: NumberFormat = numberTypeOption.format;
|
||||||
|
if (format !== NumberFormat.Num) {
|
||||||
|
throw Error('The default format should be number');
|
||||||
|
}
|
||||||
|
|
||||||
|
await databaseController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testSwitchFromMultiSelectToRichText() {
|
||||||
|
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||||
|
const databaseController = await openTestDatabase(view.id);
|
||||||
|
await databaseController.open().then((result) => result.unwrap());
|
||||||
|
|
||||||
|
// Create multi-select field
|
||||||
|
const typeOptionController = new TypeOptionController(view.id, None, FieldType.MultiSelect);
|
||||||
|
await typeOptionController.initialize();
|
||||||
|
|
||||||
|
// Insert options to first row
|
||||||
|
const row = databaseController.databaseViewCache.rowInfos[0];
|
||||||
|
const multiSelectField = typeOptionController.getFieldInfo();
|
||||||
|
// const multiSelectField = findFirstFieldInfoWithFieldType(row, FieldType.MultiSelect).unwrap();
|
||||||
|
const selectOptionCellController = await makeMultiSelectCellController(
|
||||||
|
multiSelectField.field.id,
|
||||||
|
row,
|
||||||
|
databaseController
|
||||||
|
).then((result) => result.unwrap());
|
||||||
|
const backendSvc = new SelectOptionCellBackendService(selectOptionCellController.cellIdentifier);
|
||||||
|
await backendSvc.createOption({ name: 'A' });
|
||||||
|
await backendSvc.createOption({ name: 'B' });
|
||||||
|
await backendSvc.createOption({ name: 'C' });
|
||||||
|
|
||||||
|
const selectOptionCellData = await selectOptionCellController.getCellData().then((result) => result.unwrap());
|
||||||
|
if (selectOptionCellData.options.length !== 3) {
|
||||||
|
throw Error('The options should equal to 3');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectOptionCellData.select_options.length !== 3) {
|
||||||
|
throw Error('The selected options should equal to 3');
|
||||||
|
}
|
||||||
|
await selectOptionCellController.dispose();
|
||||||
|
|
||||||
|
// Switch to RichText field type
|
||||||
|
await typeOptionController.switchToField(FieldType.RichText).then((result) => result.unwrap());
|
||||||
|
if (typeOptionController.fieldType !== FieldType.RichText) {
|
||||||
|
throw Error('The field type should be text');
|
||||||
|
}
|
||||||
|
|
||||||
|
const textCellController = await makeTextCellController(multiSelectField.field.id, row, databaseController).then(
|
||||||
|
(result) => result.unwrap()
|
||||||
|
);
|
||||||
|
const cellContent = await textCellController.getCellData();
|
||||||
|
if (cellContent.unwrap() !== 'A,B,C') {
|
||||||
|
throw Error('The cell content should be A,B,C, but receive: ' + cellContent.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
await databaseController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testEditField() {
|
||||||
|
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||||
|
const databaseController = await openTestDatabase(view.id);
|
||||||
|
await databaseController.open().then((result) => result.unwrap());
|
||||||
|
const fieldInfos = databaseController.fieldController.fieldInfos;
|
||||||
|
|
||||||
|
// Modify the name of the field
|
||||||
|
const firstFieldInfo = fieldInfos[0];
|
||||||
|
const controller = new TypeOptionController(view.id, Some(firstFieldInfo));
|
||||||
|
await controller.initialize();
|
||||||
|
const newName = 'hello world';
|
||||||
|
await controller.setFieldName(newName);
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||||
|
await assertFieldName(view.id, firstFieldInfo.field.id, firstFieldInfo.field.field_type, newName);
|
||||||
|
await databaseController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testCreateNewField() {
|
||||||
|
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||||
|
const databaseController = await openTestDatabase(view.id);
|
||||||
|
await databaseController.open().then((result) => result.unwrap());
|
||||||
|
await assertNumberOfFields(view.id, 3);
|
||||||
|
|
||||||
|
// Modify the name of the field
|
||||||
|
const controller = new TypeOptionController(view.id, None);
|
||||||
|
await controller.initialize();
|
||||||
|
await assertNumberOfFields(view.id, 4);
|
||||||
|
await databaseController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testDeleteField() {
|
||||||
|
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||||
|
const databaseController = await openTestDatabase(view.id);
|
||||||
|
await databaseController.open().then((result) => result.unwrap());
|
||||||
|
|
||||||
|
// Modify the name of the field.
|
||||||
|
// The fieldInfos[0] is the primary field by default, we can't delete it.
|
||||||
|
// So let choose the second fieldInfo.
|
||||||
|
const fieldInfo = databaseController.fieldController.fieldInfos[1];
|
||||||
|
const controller = new TypeOptionController(view.id, Some(fieldInfo));
|
||||||
|
await controller.initialize();
|
||||||
|
await assertNumberOfFields(view.id, 3);
|
||||||
|
await controller.deleteField();
|
||||||
|
await assertNumberOfFields(view.id, 2);
|
||||||
|
await databaseController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TestCreateGrid = () => {
|
||||||
|
return TestButton('Test create build-in grid', createBuildInGrid);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TestEditCell = () => {
|
||||||
|
return TestButton('Test editing cell', testEditGridRow);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TestCreateRow = () => {
|
||||||
|
return TestButton('Test create row', testCreateRow);
|
||||||
|
};
|
||||||
|
export const TestDeleteRow = () => {
|
||||||
|
return TestButton('Test delete row', testDeleteRow);
|
||||||
|
};
|
||||||
|
export const TestCreateSelectOptionInCell = () => {
|
||||||
|
return TestButton('Test create a select option in cell', testCreateOptionInCell);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TestGetSingleSelectFieldData = () => {
|
||||||
|
return TestButton('Test get single-select column data', testGetSingleSelectFieldData);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TestSwitchFromSingleSelectToNumber = () => {
|
||||||
|
return TestButton('Test switch from single-select to number column', testSwitchFromSingleSelectToNumber);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TestSwitchFromMultiSelectToText = () => {
|
||||||
|
return TestButton('Test switch from multi-select to text column', testSwitchFromMultiSelectToRichText);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TestEditField = () => {
|
||||||
|
return TestButton('Test edit the column name', testEditField);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TestCreateNewField = () => {
|
||||||
|
return TestButton('Test create a new column', testCreateNewField);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TestDeleteField = () => {
|
||||||
|
return TestButton('Test delete a new column', testDeleteField);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TestButton = (title: string, onClick: () => void) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<div>
|
||||||
|
<button className='rounded-md bg-blue-400 p-4' type='button' onClick={() => onClick()}>
|
||||||
|
{title}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,150 @@
|
|||||||
|
import {
|
||||||
|
assert,
|
||||||
|
assertNumberOfRowsInGroup,
|
||||||
|
createSingleSelectOptions,
|
||||||
|
createTestDatabaseView,
|
||||||
|
openTestDatabase,
|
||||||
|
} from './DatabaseTestHelper';
|
||||||
|
import { FieldType, ViewLayoutTypePB } from '../../../services/backend';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const TestAllKanbanTests = () => {
|
||||||
|
async function run() {
|
||||||
|
await createBuildInBoard();
|
||||||
|
await createKanbanBoardRow();
|
||||||
|
await moveKanbanBoardRow();
|
||||||
|
await createKanbanBoardColumn();
|
||||||
|
await createColumnInBoard();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<div>
|
||||||
|
<button className='rounded-md bg-red-400 p-4' type='button' onClick={() => run()}>
|
||||||
|
Run all kanban board tests
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function createBuildInBoard() {
|
||||||
|
const view = await createTestDatabaseView(ViewLayoutTypePB.Board);
|
||||||
|
const databaseController = await openTestDatabase(view.id);
|
||||||
|
databaseController.subscribe({
|
||||||
|
onGroupByField: (groups) => {
|
||||||
|
console.log(groups);
|
||||||
|
if (groups.length !== 4) {
|
||||||
|
throw Error('The build-in board should have 4 groups');
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(groups[0].rows.length === 0, 'The no status group should have 0 rows');
|
||||||
|
assert(groups[1].rows.length === 3, 'The first group should have 3 rows');
|
||||||
|
assert(groups[2].rows.length === 0, 'The second group should have 0 rows');
|
||||||
|
assert(groups[3].rows.length === 0, 'The third group should have 0 rows');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await databaseController.open().then((result) => result.unwrap());
|
||||||
|
await databaseController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createKanbanBoardRow() {
|
||||||
|
const view = await createTestDatabaseView(ViewLayoutTypePB.Board);
|
||||||
|
const databaseController = await openTestDatabase(view.id);
|
||||||
|
await databaseController.open().then((result) => result.unwrap());
|
||||||
|
|
||||||
|
// Create row in no status group
|
||||||
|
const noStatusGroup = databaseController.groups.getValue()[0];
|
||||||
|
await noStatusGroup.createRow().then((result) => result.unwrap());
|
||||||
|
await assertNumberOfRowsInGroup(view.id, noStatusGroup.groupId, 1);
|
||||||
|
|
||||||
|
await databaseController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function moveKanbanBoardRow() {
|
||||||
|
const view = await createTestDatabaseView(ViewLayoutTypePB.Board);
|
||||||
|
const databaseController = await openTestDatabase(view.id);
|
||||||
|
await databaseController.open().then((result) => result.unwrap());
|
||||||
|
|
||||||
|
// Create row in no status group
|
||||||
|
const firstGroup = databaseController.groups.getValue()[1];
|
||||||
|
const secondGroup = databaseController.groups.getValue()[2];
|
||||||
|
const row = firstGroup.rowAtIndex(0).unwrap();
|
||||||
|
await databaseController.moveRow(row.id, secondGroup.groupId);
|
||||||
|
|
||||||
|
assert(firstGroup.rows.length === 2);
|
||||||
|
await assertNumberOfRowsInGroup(view.id, firstGroup.groupId, 2);
|
||||||
|
|
||||||
|
assert(secondGroup.rows.length === 1);
|
||||||
|
await assertNumberOfRowsInGroup(view.id, secondGroup.groupId, 1);
|
||||||
|
|
||||||
|
await databaseController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createKanbanBoardColumn() {
|
||||||
|
const view = await createTestDatabaseView(ViewLayoutTypePB.Board);
|
||||||
|
const databaseController = await openTestDatabase(view.id);
|
||||||
|
await databaseController.open().then((result) => result.unwrap());
|
||||||
|
|
||||||
|
// Create row in no status group
|
||||||
|
const firstGroup = databaseController.groups.getValue()[1];
|
||||||
|
const secondGroup = databaseController.groups.getValue()[2];
|
||||||
|
await databaseController.moveGroup(firstGroup.groupId, secondGroup.groupId);
|
||||||
|
|
||||||
|
assert(databaseController.groups.getValue()[1].groupId === secondGroup.groupId);
|
||||||
|
assert(databaseController.groups.getValue()[2].groupId === firstGroup.groupId);
|
||||||
|
await databaseController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createColumnInBoard() {
|
||||||
|
const view = await createTestDatabaseView(ViewLayoutTypePB.Board);
|
||||||
|
const databaseController = await openTestDatabase(view.id);
|
||||||
|
await databaseController.open().then((result) => result.unwrap());
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const singleSelect = databaseController.fieldController.fieldInfos.find(
|
||||||
|
(fieldInfo) => fieldInfo.field.field_type === FieldType.SingleSelect
|
||||||
|
)!;
|
||||||
|
|
||||||
|
// Create a option which will cause creating a new group
|
||||||
|
const name = 'New column';
|
||||||
|
await createSingleSelectOptions(view.id, singleSelect, [name]);
|
||||||
|
|
||||||
|
// Wait the backend posting the notification to update the groups
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||||
|
assert(databaseController.groups.value.length === 5, 'expect number of groups is 5');
|
||||||
|
assert(databaseController.groups.value[4].name === name, 'expect the last group name is ' + name);
|
||||||
|
await databaseController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TestCreateKanbanBoard = () => {
|
||||||
|
return TestButton('Test create build-in board', createBuildInBoard);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TestCreateKanbanBoardRowInNoStatusGroup = () => {
|
||||||
|
return TestButton('Test create row in build-in kanban board', createKanbanBoardRow);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TestMoveKanbanBoardRow = () => {
|
||||||
|
return TestButton('Test move row in build-in kanban board', moveKanbanBoardRow);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TestMoveKanbanBoardColumn = () => {
|
||||||
|
return TestButton('Test move column in build-in kanban board', createKanbanBoardColumn);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TestCreateKanbanBoardColumn = () => {
|
||||||
|
return TestButton('Test create column in build-in kanban board', createColumnInBoard);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TestButton = (title: string, onClick: () => void) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<div>
|
||||||
|
<button className='rounded-md bg-yellow-200 p-4' type='button' onClick={() => onClick()}>
|
||||||
|
{title}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
@ -1,7 +1,16 @@
|
|||||||
import {
|
import {
|
||||||
|
CreateBoardCardPayloadPB,
|
||||||
|
DatabaseEventCreateBoardCard,
|
||||||
DatabaseEventCreateRow,
|
DatabaseEventCreateRow,
|
||||||
DatabaseEventGetDatabase,
|
DatabaseEventGetDatabase,
|
||||||
DatabaseEventGetFields,
|
DatabaseEventGetFields,
|
||||||
|
DatabaseEventGetGroup,
|
||||||
|
DatabaseEventGetGroups,
|
||||||
|
DatabaseEventMoveGroup,
|
||||||
|
DatabaseEventMoveGroupRow,
|
||||||
|
DatabaseGroupIdPB,
|
||||||
|
MoveGroupPayloadPB,
|
||||||
|
MoveGroupRowPayloadPB,
|
||||||
} from '../../../../services/backend/events/flowy-database';
|
} from '../../../../services/backend/events/flowy-database';
|
||||||
import {
|
import {
|
||||||
GetFieldPayloadPB,
|
GetFieldPayloadPB,
|
||||||
@ -37,6 +46,31 @@ export class DatabaseBackendService {
|
|||||||
return DatabaseEventCreateRow(payload);
|
return DatabaseEventCreateRow(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
createGroupRow = async (groupId: string, startRowId?: string) => {
|
||||||
|
const payload = CreateBoardCardPayloadPB.fromObject({ view_id: this.viewId, group_id: groupId });
|
||||||
|
if (startRowId !== undefined) {
|
||||||
|
payload.start_row_id = startRowId;
|
||||||
|
}
|
||||||
|
return DatabaseEventCreateBoardCard(payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
moveRow = (rowId: string, groupId?: string) => {
|
||||||
|
const payload = MoveGroupRowPayloadPB.fromObject({ view_id: this.viewId, from_row_id: rowId });
|
||||||
|
if (groupId !== undefined) {
|
||||||
|
payload.to_group_id = groupId;
|
||||||
|
}
|
||||||
|
return DatabaseEventMoveGroupRow(payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
moveGroup = (fromGroupId: string, toGroupId: string) => {
|
||||||
|
const payload = MoveGroupPayloadPB.fromObject({
|
||||||
|
view_id: this.viewId,
|
||||||
|
from_group_id: fromGroupId,
|
||||||
|
to_group_id: toGroupId,
|
||||||
|
});
|
||||||
|
return DatabaseEventMoveGroup(payload);
|
||||||
|
};
|
||||||
|
|
||||||
getFields = async (fieldIds?: FieldIdPB[]) => {
|
getFields = async (fieldIds?: FieldIdPB[]) => {
|
||||||
const payload = GetFieldPayloadPB.fromObject({ view_id: this.viewId });
|
const payload = GetFieldPayloadPB.fromObject({ view_id: this.viewId });
|
||||||
|
|
||||||
@ -46,4 +80,14 @@ export class DatabaseBackendService {
|
|||||||
|
|
||||||
return DatabaseEventGetFields(payload).then((result) => result.map((value) => value.items));
|
return DatabaseEventGetFields(payload).then((result) => result.map((value) => value.items));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getGroup = (groupId: string) => {
|
||||||
|
const payload = DatabaseGroupIdPB.fromObject({ view_id: this.viewId, group_id: groupId });
|
||||||
|
return DatabaseEventGetGroup(payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
loadGroups = () => {
|
||||||
|
const payload = DatabaseViewIdPB.fromObject({ value: this.viewId });
|
||||||
|
return DatabaseEventGetGroups(payload);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,55 +1,150 @@
|
|||||||
import { DatabaseBackendService } from './database_bd_svc';
|
import { DatabaseBackendService } from './database_bd_svc';
|
||||||
import { FieldController, FieldInfo } from './field/field_controller';
|
import { FieldController, FieldInfo } from './field/field_controller';
|
||||||
import { DatabaseViewCache } from './view/database_view_cache';
|
import { DatabaseViewCache } from './view/database_view_cache';
|
||||||
import { DatabasePB } from '../../../../services/backend';
|
import { DatabasePB, GroupPB } from '../../../../services/backend';
|
||||||
import { RowChangedReason, RowInfo } from './row/row_cache';
|
import { RowChangedReason, RowInfo } from './row/row_cache';
|
||||||
import { Err, Ok } from 'ts-results';
|
import { Err } from 'ts-results';
|
||||||
|
import { DatabaseGroupController } from './group/group_controller';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { DatabaseGroupObserver } from './group/group_observer';
|
||||||
|
import { Log } from '../../../utils/log';
|
||||||
|
|
||||||
export type SubscribeCallbacks = {
|
export type DatabaseSubscriberCallbacks = {
|
||||||
onViewChanged?: (data: DatabasePB) => void;
|
onViewChanged?: (data: DatabasePB) => void;
|
||||||
onRowsChanged?: (rowInfos: readonly RowInfo[], reason: RowChangedReason) => void;
|
onRowsChanged?: (rowInfos: readonly RowInfo[], reason: RowChangedReason) => void;
|
||||||
onFieldsChanged?: (fieldInfos: readonly FieldInfo[]) => void;
|
onFieldsChanged?: (fieldInfos: readonly FieldInfo[]) => void;
|
||||||
|
onGroupByField?: (groups: GroupPB[]) => void;
|
||||||
|
|
||||||
|
onNumOfGroupChanged?: {
|
||||||
|
onUpdateGroup: (value: GroupPB[]) => void;
|
||||||
|
onDeleteGroup: (value: GroupPB[]) => void;
|
||||||
|
onInsertGroup: (value: GroupPB[]) => void;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export class DatabaseController {
|
export class DatabaseController {
|
||||||
private backendService: DatabaseBackendService;
|
private readonly backendService: DatabaseBackendService;
|
||||||
fieldController: FieldController;
|
fieldController: FieldController;
|
||||||
databaseViewCache: DatabaseViewCache;
|
databaseViewCache: DatabaseViewCache;
|
||||||
private _callback?: SubscribeCallbacks;
|
private _callback?: DatabaseSubscriberCallbacks;
|
||||||
|
public groups: BehaviorSubject<DatabaseGroupController[]>;
|
||||||
|
private groupsObserver: DatabaseGroupObserver;
|
||||||
|
|
||||||
constructor(public readonly viewId: string) {
|
constructor(public readonly viewId: string) {
|
||||||
this.backendService = new DatabaseBackendService(viewId);
|
this.backendService = new DatabaseBackendService(viewId);
|
||||||
this.fieldController = new FieldController(viewId);
|
this.fieldController = new FieldController(viewId);
|
||||||
this.databaseViewCache = new DatabaseViewCache(viewId, this.fieldController);
|
this.databaseViewCache = new DatabaseViewCache(viewId, this.fieldController);
|
||||||
|
this.groups = new BehaviorSubject<DatabaseGroupController[]>([]);
|
||||||
|
this.groupsObserver = new DatabaseGroupObserver(viewId);
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe = (callbacks: SubscribeCallbacks) => {
|
subscribe = (callbacks: DatabaseSubscriberCallbacks) => {
|
||||||
this._callback = callbacks;
|
this._callback = callbacks;
|
||||||
this.fieldController.subscribeOnNumOfFieldsChanged(callbacks.onFieldsChanged);
|
this.fieldController.subscribe({ onNumOfFieldsChanged: callbacks.onFieldsChanged });
|
||||||
this.databaseViewCache.getRowCache().subscribeOnRowsChanged((reason) => {
|
this.databaseViewCache.getRowCache().subscribe({
|
||||||
this._callback?.onRowsChanged?.(this.databaseViewCache.rowInfos, reason);
|
onRowsChanged: (reason) => {
|
||||||
|
this._callback?.onRowsChanged?.(this.databaseViewCache.rowInfos, reason);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
open = async () => {
|
open = async () => {
|
||||||
const result = await this.backendService.openDatabase();
|
const openDatabaseResult = await this.backendService.openDatabase();
|
||||||
if (result.ok) {
|
if (openDatabaseResult.ok) {
|
||||||
const database: DatabasePB = result.val;
|
const database: DatabasePB = openDatabaseResult.val;
|
||||||
this._callback?.onViewChanged?.(database);
|
await this.databaseViewCache.initialize();
|
||||||
|
await this.fieldController.initialize();
|
||||||
|
|
||||||
|
// subscriptions
|
||||||
|
await this.subscribeOnGroupsChanged();
|
||||||
|
|
||||||
|
// load database initial data
|
||||||
await this.fieldController.loadFields(database.fields);
|
await this.fieldController.loadFields(database.fields);
|
||||||
await this.databaseViewCache.listenOnRowsChanged();
|
const loadGroupResult = await this.loadGroup();
|
||||||
await this.fieldController.listenOnFieldChanges();
|
|
||||||
this.databaseViewCache.initializeWithRows(database.rows);
|
this.databaseViewCache.initializeWithRows(database.rows);
|
||||||
return Ok.EMPTY;
|
|
||||||
|
this._callback?.onViewChanged?.(database);
|
||||||
|
return loadGroupResult;
|
||||||
} else {
|
} else {
|
||||||
return Err(result.val);
|
return Err(openDatabaseResult.val);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
createRow = async () => {
|
createRow = () => {
|
||||||
return this.backendService.createRow();
|
return this.backendService.createRow();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
moveRow = (rowId: string, groupId: string) => {
|
||||||
|
return this.backendService.moveRow(rowId, groupId);
|
||||||
|
};
|
||||||
|
|
||||||
|
moveGroup = (fromGroupId: string, toGroupId: string) => {
|
||||||
|
return this.backendService.moveGroup(fromGroupId, toGroupId);
|
||||||
|
};
|
||||||
|
|
||||||
|
private loadGroup = async () => {
|
||||||
|
const result = await this.backendService.loadGroups();
|
||||||
|
if (result.ok) {
|
||||||
|
const groups = result.val.items;
|
||||||
|
await this.initialGroups(groups);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
private initialGroups = async (groups: GroupPB[]) => {
|
||||||
|
this.groups.getValue().forEach((controller) => {
|
||||||
|
void controller.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
const controllers: DatabaseGroupController[] = [];
|
||||||
|
for (const groupPB of groups) {
|
||||||
|
const controller = new DatabaseGroupController(groupPB, this.backendService);
|
||||||
|
await controller.initialize();
|
||||||
|
controllers.push(controller);
|
||||||
|
}
|
||||||
|
this.groups.next(controllers);
|
||||||
|
this.groups.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
private subscribeOnGroupsChanged = async () => {
|
||||||
|
await this.groupsObserver.subscribe({
|
||||||
|
onGroupBy: async (result) => {
|
||||||
|
if (result.ok) {
|
||||||
|
await this.initialGroups(result.val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onGroupChangeset: (result) => {
|
||||||
|
if (result.err) {
|
||||||
|
Log.error(result.val);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const changeset = result.val;
|
||||||
|
let existControllers = [...this.groups.getValue()];
|
||||||
|
for (const deleteId of changeset.deleted_groups) {
|
||||||
|
existControllers = existControllers.filter((c) => c.groupId !== deleteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const update of changeset.update_groups) {
|
||||||
|
const index = existControllers.findIndex((c) => c.groupId === update.group_id);
|
||||||
|
if (index !== -1) {
|
||||||
|
existControllers[index].updateGroup(update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const insert of changeset.inserted_groups) {
|
||||||
|
const controller = new DatabaseGroupController(insert.group, this.backendService);
|
||||||
|
if (insert.index > existControllers.length) {
|
||||||
|
existControllers.push(controller);
|
||||||
|
} else {
|
||||||
|
existControllers.splice(insert.index, 0, controller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.groups.next(existControllers);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
dispose = async () => {
|
dispose = async () => {
|
||||||
await this.backendService.closeDatabase();
|
await this.backendService.closeDatabase();
|
||||||
await this.fieldController.dispose();
|
await this.fieldController.dispose();
|
||||||
|
@ -6,17 +6,17 @@ import { ChangeNotifier } from '../../../../utils/change_notifier';
|
|||||||
|
|
||||||
export class FieldController {
|
export class FieldController {
|
||||||
private backendService: DatabaseBackendService;
|
private backendService: DatabaseBackendService;
|
||||||
private numOfFieldsObserver: DatabaseFieldChangesetObserver;
|
private fieldChangesetObserver: DatabaseFieldChangesetObserver;
|
||||||
private numOfFieldsNotifier = new NumOfFieldsNotifier([]);
|
private numOfFieldsNotifier = new NumOfFieldsNotifier([]);
|
||||||
|
|
||||||
constructor(public readonly viewId: string) {
|
constructor(public readonly viewId: string) {
|
||||||
this.backendService = new DatabaseBackendService(viewId);
|
this.backendService = new DatabaseBackendService(viewId);
|
||||||
this.numOfFieldsObserver = new DatabaseFieldChangesetObserver(viewId);
|
this.fieldChangesetObserver = new DatabaseFieldChangesetObserver(viewId);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose = async () => {
|
dispose = async () => {
|
||||||
this.numOfFieldsNotifier.unsubscribe();
|
this.numOfFieldsNotifier.unsubscribe();
|
||||||
await this.numOfFieldsObserver.unsubscribe();
|
await this.fieldChangesetObserver.unsubscribe();
|
||||||
};
|
};
|
||||||
|
|
||||||
get fieldInfos(): readonly FieldInfo[] {
|
get fieldInfos(): readonly FieldInfo[] {
|
||||||
@ -36,14 +36,14 @@ export class FieldController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
subscribeOnNumOfFieldsChanged = (callback?: (fieldInfos: readonly FieldInfo[]) => void) => {
|
subscribe = (callbacks: { onNumOfFieldsChanged?: (fieldInfos: readonly FieldInfo[]) => void}) => {
|
||||||
return this.numOfFieldsNotifier.observer.subscribe((fieldInfos) => {
|
this.numOfFieldsNotifier.observer.subscribe((fieldInfos) => {
|
||||||
callback?.(fieldInfos);
|
callbacks.onNumOfFieldsChanged?.(fieldInfos);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
listenOnFieldChanges = async () => {
|
initialize = async () => {
|
||||||
await this.numOfFieldsObserver.subscribe({
|
await this.fieldChangesetObserver.subscribe({
|
||||||
onFieldsChanged: (result) => {
|
onFieldsChanged: (result) => {
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
const changeset = result.val;
|
const changeset = result.val;
|
||||||
|
@ -3,16 +3,15 @@ import { DatabaseNotification, DatabaseFieldChangesetPB, FlowyError, FieldPB } f
|
|||||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
||||||
import { DatabaseNotificationObserver } from '../notifications/observer';
|
import { DatabaseNotificationObserver } from '../notifications/observer';
|
||||||
|
|
||||||
type UpdateFieldNotifiedValue = Result<DatabaseFieldChangesetPB, FlowyError>;
|
export type FieldChangesetSubscribeCallback = (value: Result<DatabaseFieldChangesetPB, FlowyError>) => void;
|
||||||
export type DatabaseNotificationCallback = (value: UpdateFieldNotifiedValue) => void;
|
|
||||||
|
|
||||||
export class DatabaseFieldChangesetObserver {
|
export class DatabaseFieldChangesetObserver {
|
||||||
private notifier?: ChangeNotifier<UpdateFieldNotifiedValue>;
|
private notifier?: ChangeNotifier<Result<DatabaseFieldChangesetPB, FlowyError>>;
|
||||||
private listener?: DatabaseNotificationObserver;
|
private listener?: DatabaseNotificationObserver;
|
||||||
|
|
||||||
constructor(public readonly viewId: string) {}
|
constructor(public readonly viewId: string) {}
|
||||||
|
|
||||||
subscribe = async (callbacks: { onFieldsChanged: DatabaseNotificationCallback }) => {
|
subscribe = async (callbacks: { onFieldsChanged: FieldChangesetSubscribeCallback }) => {
|
||||||
this.notifier = new ChangeNotifier();
|
this.notifier = new ChangeNotifier();
|
||||||
this.notifier?.observer.subscribe(callbacks.onFieldsChanged);
|
this.notifier?.observer.subscribe(callbacks.onFieldsChanged);
|
||||||
|
|
||||||
@ -41,16 +40,15 @@ export class DatabaseFieldChangesetObserver {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type FieldNotifiedValue = Result<FieldPB, FlowyError>;
|
export type FieldSubscribeCallback = (value: Result<FieldPB, FlowyError>) => void;
|
||||||
export type FieldNotificationCallback = (value: FieldNotifiedValue) => void;
|
|
||||||
|
|
||||||
export class DatabaseFieldObserver {
|
export class DatabaseFieldObserver {
|
||||||
private _notifier?: ChangeNotifier<FieldNotifiedValue>;
|
private _notifier?: ChangeNotifier<Result<FieldPB, FlowyError>>;
|
||||||
private _listener?: DatabaseNotificationObserver;
|
private _listener?: DatabaseNotificationObserver;
|
||||||
|
|
||||||
constructor(public readonly fieldId: string) {}
|
constructor(public readonly fieldId: string) {}
|
||||||
|
|
||||||
subscribe = async (callbacks: { onFieldChanged: FieldNotificationCallback }) => {
|
subscribe = async (callbacks: { onFieldChanged: FieldSubscribeCallback }) => {
|
||||||
this._notifier = new ChangeNotifier();
|
this._notifier = new ChangeNotifier();
|
||||||
this._notifier?.observer.subscribe(callbacks.onFieldChanged);
|
this._notifier?.observer.subscribe(callbacks.onFieldChanged);
|
||||||
|
|
||||||
|
@ -0,0 +1,149 @@
|
|||||||
|
import {
|
||||||
|
DatabaseNotification,
|
||||||
|
FlowyError,
|
||||||
|
GroupPB,
|
||||||
|
GroupRowsNotificationPB,
|
||||||
|
RowPB,
|
||||||
|
} from '../../../../../services/backend';
|
||||||
|
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
||||||
|
import { None, Ok, Option, Result, Some } from 'ts-results';
|
||||||
|
import { DatabaseNotificationObserver } from '../notifications/observer';
|
||||||
|
import { Log } from '../../../../utils/log';
|
||||||
|
import { DatabaseBackendService } from '../database_bd_svc';
|
||||||
|
|
||||||
|
export type GroupDataCallbacks = {
|
||||||
|
onRemoveRow: (groupId: string, rowId: string) => void;
|
||||||
|
onInsertRow: (groupId: string, row: RowPB, index?: number) => void;
|
||||||
|
onUpdateRow: (groupId: string, row: RowPB) => void;
|
||||||
|
|
||||||
|
onCreateRow: (groupId: string, row: RowPB) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class DatabaseGroupController {
|
||||||
|
private dataObserver: GroupDataObserver;
|
||||||
|
private callbacks?: GroupDataCallbacks;
|
||||||
|
|
||||||
|
constructor(private group: GroupPB, private databaseBackendSvc: DatabaseBackendService) {
|
||||||
|
this.dataObserver = new GroupDataObserver(group.group_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
get groupId() {
|
||||||
|
return this.group.group_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
get rows() {
|
||||||
|
return this.group.rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return this.group.desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGroup = (group: GroupPB) => {
|
||||||
|
this.group = group;
|
||||||
|
};
|
||||||
|
|
||||||
|
rowAtIndex = (index: number): Option<RowPB> => {
|
||||||
|
if (this.group.rows.length < index) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
return Some(this.group.rows[index]);
|
||||||
|
};
|
||||||
|
|
||||||
|
initialize = async () => {
|
||||||
|
await this.dataObserver.subscribe({
|
||||||
|
onRowsChanged: (result) => {
|
||||||
|
if (result.ok) {
|
||||||
|
const changeset = result.val;
|
||||||
|
// Delete
|
||||||
|
changeset.deleted_rows.forEach((deletedRowId) => {
|
||||||
|
this.group.rows = this.group.rows.filter((row) => row.id !== deletedRowId);
|
||||||
|
this.callbacks?.onRemoveRow(this.group.group_id, deletedRowId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Insert
|
||||||
|
changeset.inserted_rows.forEach((insertedRow) => {
|
||||||
|
let index: number | undefined = insertedRow.index;
|
||||||
|
if (insertedRow.has_index && this.group.rows.length > insertedRow.index) {
|
||||||
|
this.group.rows.splice(index, 0, insertedRow.row);
|
||||||
|
} else {
|
||||||
|
index = undefined;
|
||||||
|
this.group.rows.push(insertedRow.row);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insertedRow.is_new) {
|
||||||
|
this.callbacks?.onCreateRow(this.group.group_id, insertedRow.row);
|
||||||
|
} else {
|
||||||
|
this.callbacks?.onInsertRow(this.group.group_id, insertedRow.row, index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update
|
||||||
|
changeset.updated_rows.forEach((updatedRow) => {
|
||||||
|
const index = this.group.rows.findIndex((row) => row.id === updatedRow.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.group.rows[index] = updatedRow;
|
||||||
|
this.callbacks?.onUpdateRow(this.group.group_id, updatedRow);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Log.error(result.val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
createRow = async () => {
|
||||||
|
return this.databaseBackendSvc.createGroupRow(this.group.group_id);
|
||||||
|
};
|
||||||
|
|
||||||
|
subscribe = (callbacks: GroupDataCallbacks) => {
|
||||||
|
this.callbacks = callbacks;
|
||||||
|
};
|
||||||
|
|
||||||
|
unsubscribe = () => {
|
||||||
|
this.callbacks = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
dispose = async () => {
|
||||||
|
await this.dataObserver.unsubscribe();
|
||||||
|
this.callbacks = undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupRowsSubscribeCallback = (value: Result<GroupRowsNotificationPB, FlowyError>) => void;
|
||||||
|
|
||||||
|
class GroupDataObserver {
|
||||||
|
private notifier?: ChangeNotifier<Result<GroupRowsNotificationPB, FlowyError>>;
|
||||||
|
private listener?: DatabaseNotificationObserver;
|
||||||
|
|
||||||
|
constructor(public readonly groupId: string) {}
|
||||||
|
|
||||||
|
subscribe = async (callbacks: { onRowsChanged: GroupRowsSubscribeCallback }) => {
|
||||||
|
this.notifier = new ChangeNotifier();
|
||||||
|
this.notifier?.observer.subscribe(callbacks.onRowsChanged);
|
||||||
|
|
||||||
|
this.listener = new DatabaseNotificationObserver({
|
||||||
|
id: this.groupId,
|
||||||
|
parserHandler: (notification, result) => {
|
||||||
|
switch (notification) {
|
||||||
|
case DatabaseNotification.DidUpdateGroupRow:
|
||||||
|
if (result.ok) {
|
||||||
|
this.notifier?.notify(Ok(GroupRowsNotificationPB.deserializeBinary(result.val)));
|
||||||
|
} else {
|
||||||
|
this.notifier?.notify(result);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await this.listener.start();
|
||||||
|
};
|
||||||
|
|
||||||
|
unsubscribe = async () => {
|
||||||
|
await this.listener?.stop();
|
||||||
|
this.notifier?.unsubscribe();
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
||||||
|
import { Ok, Result } from 'ts-results';
|
||||||
|
import { DatabaseNotification, FlowyError, GroupChangesetPB, GroupPB } from '../../../../../services/backend';
|
||||||
|
import { DatabaseNotificationObserver } from '../notifications/observer';
|
||||||
|
|
||||||
|
export type GroupByFieldCallback = (value: Result<GroupPB[], FlowyError>) => void;
|
||||||
|
export type GroupChangesetSubscribeCallback = (value: Result<GroupChangesetPB, FlowyError>) => void;
|
||||||
|
|
||||||
|
export class DatabaseGroupObserver {
|
||||||
|
private groupByNotifier?: ChangeNotifier<Result<GroupPB[], FlowyError>>;
|
||||||
|
private groupChangesetNotifier?: ChangeNotifier<Result<GroupChangesetPB, FlowyError>>;
|
||||||
|
private listener?: DatabaseNotificationObserver;
|
||||||
|
|
||||||
|
constructor(public readonly viewId: string) {}
|
||||||
|
|
||||||
|
subscribe = async (callbacks: {
|
||||||
|
onGroupBy: GroupByFieldCallback;
|
||||||
|
onGroupChangeset: GroupChangesetSubscribeCallback;
|
||||||
|
}) => {
|
||||||
|
this.groupByNotifier = new ChangeNotifier();
|
||||||
|
this.groupByNotifier?.observer.subscribe(callbacks.onGroupBy);
|
||||||
|
|
||||||
|
this.groupChangesetNotifier = new ChangeNotifier();
|
||||||
|
this.groupChangesetNotifier?.observer.subscribe(callbacks.onGroupChangeset);
|
||||||
|
|
||||||
|
this.listener = new DatabaseNotificationObserver({
|
||||||
|
id: this.viewId,
|
||||||
|
parserHandler: (notification, result) => {
|
||||||
|
switch (notification) {
|
||||||
|
case DatabaseNotification.DidGroupByField:
|
||||||
|
if (result.ok) {
|
||||||
|
this.groupByNotifier?.notify(Ok(GroupChangesetPB.deserializeBinary(result.val).initial_groups));
|
||||||
|
} else {
|
||||||
|
this.groupByNotifier?.notify(result);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DatabaseNotification.DidUpdateGroups:
|
||||||
|
if (result.ok) {
|
||||||
|
this.groupChangesetNotifier?.notify(Ok(GroupChangesetPB.deserializeBinary(result.val)));
|
||||||
|
} else {
|
||||||
|
this.groupChangesetNotifier?.notify(result);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.listener.start();
|
||||||
|
};
|
||||||
|
|
||||||
|
unsubscribe = async () => {
|
||||||
|
this.groupByNotifier?.unsubscribe();
|
||||||
|
this.groupChangesetNotifier?.unsubscribe();
|
||||||
|
await this.listener?.stop();
|
||||||
|
};
|
||||||
|
}
|
@ -53,12 +53,14 @@ export class RowCache {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
subscribeOnRowsChanged = (callback: (reason: RowChangedReason, cellMap?: Map<string, CellIdentifier>) => void) => {
|
subscribe = (callbacks: {
|
||||||
|
onRowsChanged: (reason: RowChangedReason, cellMap?: Map<string, CellIdentifier>) => void;
|
||||||
|
}) => {
|
||||||
return this.notifier.observer.subscribe((change) => {
|
return this.notifier.observer.subscribe((change) => {
|
||||||
if (change.rowId !== undefined) {
|
if (change.rowId !== undefined) {
|
||||||
callback(change.reason, this._toCellMap(change.rowId, this.getFieldInfos()));
|
callbacks.onRowsChanged(change.reason, this._toCellMap(change.rowId, this.getFieldInfos()));
|
||||||
} else {
|
} else {
|
||||||
callback(change.reason);
|
callbacks.onRowsChanged(change.reason);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -7,16 +7,17 @@ import { Subscription } from 'rxjs';
|
|||||||
export class DatabaseViewCache {
|
export class DatabaseViewCache {
|
||||||
private readonly rowsObserver: DatabaseViewRowsObserver;
|
private readonly rowsObserver: DatabaseViewRowsObserver;
|
||||||
private readonly rowCache: RowCache;
|
private readonly rowCache: RowCache;
|
||||||
private readonly fieldSubscription?: Subscription;
|
|
||||||
|
|
||||||
constructor(public readonly viewId: string, fieldController: FieldController) {
|
constructor(public readonly viewId: string, fieldController: FieldController) {
|
||||||
this.rowsObserver = new DatabaseViewRowsObserver(viewId);
|
this.rowsObserver = new DatabaseViewRowsObserver(viewId);
|
||||||
this.rowCache = new RowCache(viewId, () => fieldController.fieldInfos);
|
this.rowCache = new RowCache(viewId, () => fieldController.fieldInfos);
|
||||||
this.fieldSubscription = fieldController.subscribeOnNumOfFieldsChanged((fieldInfos) => {
|
fieldController.subscribe({
|
||||||
fieldInfos.forEach((fieldInfo) => {
|
onNumOfFieldsChanged: (fieldInfos) => {
|
||||||
this.rowCache.onFieldUpdated(fieldInfo);
|
fieldInfos.forEach((fieldInfo) => {
|
||||||
});
|
this.rowCache.onFieldUpdated(fieldInfo);
|
||||||
this.rowCache.onNumberOfFieldsUpdated(fieldInfos);
|
});
|
||||||
|
this.rowCache.onNumberOfFieldsUpdated(fieldInfos);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,12 +34,11 @@ export class DatabaseViewCache {
|
|||||||
};
|
};
|
||||||
|
|
||||||
dispose = async () => {
|
dispose = async () => {
|
||||||
this.fieldSubscription?.unsubscribe();
|
|
||||||
await this.rowsObserver.unsubscribe();
|
await this.rowsObserver.unsubscribe();
|
||||||
await this.rowCache.dispose();
|
await this.rowCache.dispose();
|
||||||
};
|
};
|
||||||
|
|
||||||
listenOnRowsChanged = async () => {
|
initialize = async () => {
|
||||||
await this.rowsObserver.subscribe({
|
await this.rowsObserver.subscribe({
|
||||||
onRowsVisibilityChanged: (result) => {
|
onRowsVisibilityChanged: (result) => {
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
|
@ -1,4 +1,158 @@
|
|||||||
|
use crate::entities::parser::NotEmptyStr;
|
||||||
|
use crate::entities::{FieldIdPB, RowPB};
|
||||||
use flowy_derive::ProtoBuf;
|
use flowy_derive::ProtoBuf;
|
||||||
|
use flowy_error::ErrorCode;
|
||||||
|
|
||||||
|
/// [DatabasePB] describes how many fields and blocks the grid has
|
||||||
|
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||||
|
pub struct DatabasePB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub id: String,
|
||||||
|
|
||||||
|
#[pb(index = 2)]
|
||||||
|
pub fields: Vec<FieldIdPB>,
|
||||||
|
|
||||||
|
#[pb(index = 3)]
|
||||||
|
pub rows: Vec<RowPB>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ProtoBuf, Default)]
|
||||||
|
pub struct CreateDatabasePayloadPB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, ProtoBuf, Default, Debug)]
|
||||||
|
pub struct DatabaseViewIdPB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for DatabaseViewIdPB {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
&self.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||||
|
pub struct MoveFieldPayloadPB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub view_id: String,
|
||||||
|
|
||||||
|
#[pb(index = 2)]
|
||||||
|
pub field_id: String,
|
||||||
|
|
||||||
|
#[pb(index = 3)]
|
||||||
|
pub from_index: i32,
|
||||||
|
|
||||||
|
#[pb(index = 4)]
|
||||||
|
pub to_index: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MoveFieldParams {
|
||||||
|
pub view_id: String,
|
||||||
|
pub field_id: String,
|
||||||
|
pub from_index: i32,
|
||||||
|
pub to_index: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<MoveFieldParams> for MoveFieldPayloadPB {
|
||||||
|
type Error = ErrorCode;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<MoveFieldParams, Self::Error> {
|
||||||
|
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
|
||||||
|
let item_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::InvalidData)?;
|
||||||
|
Ok(MoveFieldParams {
|
||||||
|
view_id: view_id.0,
|
||||||
|
field_id: item_id.0,
|
||||||
|
from_index: self.from_index,
|
||||||
|
to_index: self.to_index,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||||
|
pub struct MoveRowPayloadPB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub view_id: String,
|
||||||
|
|
||||||
|
#[pb(index = 2)]
|
||||||
|
pub from_row_id: String,
|
||||||
|
|
||||||
|
#[pb(index = 4)]
|
||||||
|
pub to_row_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MoveRowParams {
|
||||||
|
pub view_id: String,
|
||||||
|
pub from_row_id: String,
|
||||||
|
pub to_row_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<MoveRowParams> for MoveRowPayloadPB {
|
||||||
|
type Error = ErrorCode;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<MoveRowParams, Self::Error> {
|
||||||
|
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
|
||||||
|
let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
|
||||||
|
let to_row_id = NotEmptyStr::parse(self.to_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
|
||||||
|
|
||||||
|
Ok(MoveRowParams {
|
||||||
|
view_id: view_id.0,
|
||||||
|
from_row_id: from_row_id.0,
|
||||||
|
to_row_id: to_row_id.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||||
|
pub struct MoveGroupRowPayloadPB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub view_id: String,
|
||||||
|
|
||||||
|
#[pb(index = 2)]
|
||||||
|
pub from_row_id: String,
|
||||||
|
|
||||||
|
#[pb(index = 3)]
|
||||||
|
pub to_group_id: String,
|
||||||
|
|
||||||
|
#[pb(index = 4, one_of)]
|
||||||
|
pub to_row_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MoveGroupRowParams {
|
||||||
|
pub view_id: String,
|
||||||
|
pub from_row_id: String,
|
||||||
|
pub to_group_id: String,
|
||||||
|
pub to_row_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<MoveGroupRowParams> for MoveGroupRowPayloadPB {
|
||||||
|
type Error = ErrorCode;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<MoveGroupRowParams, Self::Error> {
|
||||||
|
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
|
||||||
|
let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
|
||||||
|
let to_group_id =
|
||||||
|
NotEmptyStr::parse(self.to_group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?;
|
||||||
|
|
||||||
|
let to_row_id = match self.to_row_id {
|
||||||
|
None => None,
|
||||||
|
Some(to_row_id) => Some(
|
||||||
|
NotEmptyStr::parse(to_row_id)
|
||||||
|
.map_err(|_| ErrorCode::RowIdIsEmpty)?
|
||||||
|
.0,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(MoveGroupRowParams {
|
||||||
|
view_id: view_id.0,
|
||||||
|
from_row_id: from_row_id.0,
|
||||||
|
to_group_id: to_group_id.0,
|
||||||
|
to_row_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, ProtoBuf)]
|
#[derive(Debug, Default, ProtoBuf)]
|
||||||
pub struct DatabaseDescPB {
|
pub struct DatabaseDescPB {
|
||||||
@ -14,3 +168,30 @@ pub struct RepeatedDatabaseDescPB {
|
|||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
pub items: Vec<DatabaseDescPB>,
|
pub items: Vec<DatabaseDescPB>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||||
|
pub struct DatabaseGroupIdPB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub view_id: String,
|
||||||
|
|
||||||
|
#[pb(index = 2)]
|
||||||
|
pub group_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DatabaseGroupIdParams {
|
||||||
|
pub view_id: String,
|
||||||
|
pub group_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<DatabaseGroupIdParams> for DatabaseGroupIdPB {
|
||||||
|
type Error = ErrorCode;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<DatabaseGroupIdParams, Self::Error> {
|
||||||
|
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
|
||||||
|
let group_id = NotEmptyStr::parse(self.group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?;
|
||||||
|
Ok(DatabaseGroupIdParams {
|
||||||
|
view_id: view_id.0,
|
||||||
|
group_id: group_id.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,155 +0,0 @@
|
|||||||
use crate::entities::parser::NotEmptyStr;
|
|
||||||
use crate::entities::{FieldIdPB, RowPB};
|
|
||||||
use flowy_derive::ProtoBuf;
|
|
||||||
use flowy_error::ErrorCode;
|
|
||||||
|
|
||||||
/// [DatabasePB] describes how many fields and blocks the grid has
|
|
||||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
|
||||||
pub struct DatabasePB {
|
|
||||||
#[pb(index = 1)]
|
|
||||||
pub id: String,
|
|
||||||
|
|
||||||
#[pb(index = 2)]
|
|
||||||
pub fields: Vec<FieldIdPB>,
|
|
||||||
|
|
||||||
#[pb(index = 3)]
|
|
||||||
pub rows: Vec<RowPB>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(ProtoBuf, Default)]
|
|
||||||
pub struct CreateDatabasePayloadPB {
|
|
||||||
#[pb(index = 1)]
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, ProtoBuf, Default, Debug)]
|
|
||||||
pub struct DatabaseViewIdPB {
|
|
||||||
#[pb(index = 1)]
|
|
||||||
pub value: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<str> for DatabaseViewIdPB {
|
|
||||||
fn as_ref(&self) -> &str {
|
|
||||||
&self.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
|
||||||
pub struct MoveFieldPayloadPB {
|
|
||||||
#[pb(index = 1)]
|
|
||||||
pub view_id: String,
|
|
||||||
|
|
||||||
#[pb(index = 2)]
|
|
||||||
pub field_id: String,
|
|
||||||
|
|
||||||
#[pb(index = 3)]
|
|
||||||
pub from_index: i32,
|
|
||||||
|
|
||||||
#[pb(index = 4)]
|
|
||||||
pub to_index: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct MoveFieldParams {
|
|
||||||
pub view_id: String,
|
|
||||||
pub field_id: String,
|
|
||||||
pub from_index: i32,
|
|
||||||
pub to_index: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryInto<MoveFieldParams> for MoveFieldPayloadPB {
|
|
||||||
type Error = ErrorCode;
|
|
||||||
|
|
||||||
fn try_into(self) -> Result<MoveFieldParams, Self::Error> {
|
|
||||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
|
|
||||||
let item_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::InvalidData)?;
|
|
||||||
Ok(MoveFieldParams {
|
|
||||||
view_id: view_id.0,
|
|
||||||
field_id: item_id.0,
|
|
||||||
from_index: self.from_index,
|
|
||||||
to_index: self.to_index,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
|
||||||
pub struct MoveRowPayloadPB {
|
|
||||||
#[pb(index = 1)]
|
|
||||||
pub view_id: String,
|
|
||||||
|
|
||||||
#[pb(index = 2)]
|
|
||||||
pub from_row_id: String,
|
|
||||||
|
|
||||||
#[pb(index = 4)]
|
|
||||||
pub to_row_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MoveRowParams {
|
|
||||||
pub view_id: String,
|
|
||||||
pub from_row_id: String,
|
|
||||||
pub to_row_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryInto<MoveRowParams> for MoveRowPayloadPB {
|
|
||||||
type Error = ErrorCode;
|
|
||||||
|
|
||||||
fn try_into(self) -> Result<MoveRowParams, Self::Error> {
|
|
||||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
|
|
||||||
let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
|
|
||||||
let to_row_id = NotEmptyStr::parse(self.to_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
|
|
||||||
|
|
||||||
Ok(MoveRowParams {
|
|
||||||
view_id: view_id.0,
|
|
||||||
from_row_id: from_row_id.0,
|
|
||||||
to_row_id: to_row_id.0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
|
||||||
pub struct MoveGroupRowPayloadPB {
|
|
||||||
#[pb(index = 1)]
|
|
||||||
pub view_id: String,
|
|
||||||
|
|
||||||
#[pb(index = 2)]
|
|
||||||
pub from_row_id: String,
|
|
||||||
|
|
||||||
#[pb(index = 3)]
|
|
||||||
pub to_group_id: String,
|
|
||||||
|
|
||||||
#[pb(index = 4, one_of)]
|
|
||||||
pub to_row_id: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MoveGroupRowParams {
|
|
||||||
pub view_id: String,
|
|
||||||
pub from_row_id: String,
|
|
||||||
pub to_group_id: String,
|
|
||||||
pub to_row_id: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryInto<MoveGroupRowParams> for MoveGroupRowPayloadPB {
|
|
||||||
type Error = ErrorCode;
|
|
||||||
|
|
||||||
fn try_into(self) -> Result<MoveGroupRowParams, Self::Error> {
|
|
||||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
|
|
||||||
let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
|
|
||||||
let to_group_id =
|
|
||||||
NotEmptyStr::parse(self.to_group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?;
|
|
||||||
|
|
||||||
let to_row_id = match self.to_row_id {
|
|
||||||
None => None,
|
|
||||||
Some(to_row_id) => Some(
|
|
||||||
NotEmptyStr::parse(to_row_id)
|
|
||||||
.map_err(|_| ErrorCode::RowIdIsEmpty)?
|
|
||||||
.0,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(MoveGroupRowParams {
|
|
||||||
view_id: view_id.0,
|
|
||||||
from_row_id: from_row_id.0,
|
|
||||||
to_group_id: to_group_id.0,
|
|
||||||
to_row_id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,7 +3,6 @@ mod cell_entities;
|
|||||||
mod database_entities;
|
mod database_entities;
|
||||||
mod field_entities;
|
mod field_entities;
|
||||||
pub mod filter_entities;
|
pub mod filter_entities;
|
||||||
mod grid_entities;
|
|
||||||
mod group_entities;
|
mod group_entities;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
mod row_entities;
|
mod row_entities;
|
||||||
@ -14,9 +13,9 @@ mod view_entities;
|
|||||||
pub use calendar_entities::*;
|
pub use calendar_entities::*;
|
||||||
pub use cell_entities::*;
|
pub use cell_entities::*;
|
||||||
pub use database_entities::*;
|
pub use database_entities::*;
|
||||||
|
pub use database_entities::*;
|
||||||
pub use field_entities::*;
|
pub use field_entities::*;
|
||||||
pub use filter_entities::*;
|
pub use filter_entities::*;
|
||||||
pub use grid_entities::*;
|
|
||||||
pub use group_entities::*;
|
pub use group_entities::*;
|
||||||
pub use row_entities::*;
|
pub use row_entities::*;
|
||||||
pub use setting_entities::*;
|
pub use setting_entities::*;
|
||||||
|
@ -538,6 +538,17 @@ pub(crate) async fn get_groups_handler(
|
|||||||
data_result_ok(groups)
|
data_result_ok(groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||||
|
pub(crate) async fn get_group_handler(
|
||||||
|
data: AFPluginData<DatabaseGroupIdPB>,
|
||||||
|
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||||
|
) -> DataResult<GroupPB, FlowyError> {
|
||||||
|
let params: DatabaseGroupIdParams = data.into_inner().try_into()?;
|
||||||
|
let editor = manager.get_database_editor(¶ms.view_id).await?;
|
||||||
|
let group = editor.get_group(¶ms.view_id, ¶ms.group_id).await?;
|
||||||
|
data_result_ok(group)
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||||
pub(crate) async fn create_board_card_handler(
|
pub(crate) async fn create_board_card_handler(
|
||||||
data: AFPluginData<CreateBoardCardPayloadPB>,
|
data: AFPluginData<CreateBoardCardPayloadPB>,
|
||||||
|
@ -47,7 +47,8 @@ pub fn init(database_manager: Arc<DatabaseManager>) -> AFPlugin {
|
|||||||
.event(DatabaseEvent::CreateBoardCard, create_board_card_handler)
|
.event(DatabaseEvent::CreateBoardCard, create_board_card_handler)
|
||||||
.event(DatabaseEvent::MoveGroup, move_group_handler)
|
.event(DatabaseEvent::MoveGroup, move_group_handler)
|
||||||
.event(DatabaseEvent::MoveGroupRow, move_group_row_handler)
|
.event(DatabaseEvent::MoveGroupRow, move_group_row_handler)
|
||||||
.event(DatabaseEvent::GetGroup, get_groups_handler)
|
.event(DatabaseEvent::GetGroups, get_groups_handler)
|
||||||
|
.event(DatabaseEvent::GetGroup, get_group_handler)
|
||||||
// Database
|
// Database
|
||||||
.event(DatabaseEvent::GetDatabases, get_databases_handler)
|
.event(DatabaseEvent::GetDatabases, get_databases_handler)
|
||||||
// Calendar
|
// Calendar
|
||||||
@ -221,7 +222,10 @@ pub enum DatabaseEvent {
|
|||||||
UpdateDateCell = 80,
|
UpdateDateCell = 80,
|
||||||
|
|
||||||
#[event(input = "DatabaseViewIdPB", output = "RepeatedGroupPB")]
|
#[event(input = "DatabaseViewIdPB", output = "RepeatedGroupPB")]
|
||||||
GetGroup = 100,
|
GetGroups = 100,
|
||||||
|
|
||||||
|
#[event(input = "DatabaseGroupIdPB", output = "GroupPB")]
|
||||||
|
GetGroup = 101,
|
||||||
|
|
||||||
#[event(input = "CreateBoardCardPayloadPB", output = "RowPB")]
|
#[event(input = "CreateBoardCardPayloadPB", output = "RowPB")]
|
||||||
CreateBoardCard = 110,
|
CreateBoardCard = 110,
|
||||||
|
@ -207,14 +207,14 @@ impl DatabaseManager {
|
|||||||
let create_view_editor = |database_editor: Arc<DatabaseEditor>| async move {
|
let create_view_editor = |database_editor: Arc<DatabaseEditor>| async move {
|
||||||
let user_id = user.user_id()?;
|
let user_id = user.user_id()?;
|
||||||
let (view_pad, view_rev_manager) = make_database_view_revision_pad(view_id, user).await?;
|
let (view_pad, view_rev_manager) = make_database_view_revision_pad(view_id, user).await?;
|
||||||
return DatabaseViewEditor::from_pad(
|
DatabaseViewEditor::from_pad(
|
||||||
&user_id,
|
&user_id,
|
||||||
database_editor.database_view_data.clone(),
|
database_editor.database_view_data.clone(),
|
||||||
database_editor.cell_data_cache.clone(),
|
database_editor.cell_data_cache.clone(),
|
||||||
view_rev_manager,
|
view_rev_manager,
|
||||||
view_pad,
|
view_pad,
|
||||||
)
|
)
|
||||||
.await;
|
.await
|
||||||
};
|
};
|
||||||
|
|
||||||
let database_editor = self
|
let database_editor = self
|
||||||
@ -224,7 +224,7 @@ impl DatabaseManager {
|
|||||||
.get(database_id)
|
.get(database_id)
|
||||||
.cloned();
|
.cloned();
|
||||||
|
|
||||||
return match database_editor {
|
match database_editor {
|
||||||
None => {
|
None => {
|
||||||
let mut editors_by_database_id = self.editors_by_database_id.write().await;
|
let mut editors_by_database_id = self.editors_by_database_id.write().await;
|
||||||
let db_pool = self.database_user.db_pool()?;
|
let db_pool = self.database_user.db_pool()?;
|
||||||
@ -241,7 +241,7 @@ impl DatabaseManager {
|
|||||||
|
|
||||||
Ok(database_editor)
|
Ok(database_editor)
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self, pool), err)]
|
#[tracing::instrument(level = "trace", skip(self, pool), err)]
|
||||||
|
@ -924,6 +924,11 @@ impl DatabaseEditor {
|
|||||||
self.database_views.load_groups(view_id).await
|
self.database_views.load_groups(view_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||||
|
pub async fn get_group(&self, view_id: &str, group_id: &str) -> FlowyResult<GroupPB> {
|
||||||
|
self.database_views.get_group(view_id, group_id).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn create_row_rev(&self) -> FlowyResult<RowRevision> {
|
async fn create_row_rev(&self) -> FlowyResult<RowRevision> {
|
||||||
let field_revs = self.database_pad.read().await.get_field_revs(None)?;
|
let field_revs = self.database_pad.read().await.get_field_revs(None)?;
|
||||||
let block_id = self.block_id().await?;
|
let block_id = self.block_id().await?;
|
||||||
|
@ -24,7 +24,7 @@ use database_model::{
|
|||||||
use flowy_client_sync::client_database::{
|
use flowy_client_sync::client_database::{
|
||||||
make_database_view_operations, DatabaseViewRevisionChangeset, DatabaseViewRevisionPad,
|
make_database_view_operations, DatabaseViewRevisionChangeset, DatabaseViewRevisionPad,
|
||||||
};
|
};
|
||||||
use flowy_error::FlowyResult;
|
use flowy_error::{FlowyError, FlowyResult};
|
||||||
use flowy_revision::RevisionManager;
|
use flowy_revision::RevisionManager;
|
||||||
use flowy_sqlite::ConnectionPool;
|
use flowy_sqlite::ConnectionPool;
|
||||||
use flowy_task::TaskDispatcher;
|
use flowy_task::TaskDispatcher;
|
||||||
@ -379,7 +379,7 @@ impl DatabaseViewEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Only call once after grid view editor initialized
|
/// Only call once after database view editor initialized
|
||||||
#[tracing::instrument(level = "trace", skip(self))]
|
#[tracing::instrument(level = "trace", skip(self))]
|
||||||
pub async fn v_load_groups(&self) -> FlowyResult<Vec<GroupPB>> {
|
pub async fn v_load_groups(&self) -> FlowyResult<Vec<GroupPB>> {
|
||||||
let groups = self
|
let groups = self
|
||||||
@ -394,6 +394,14 @@ impl DatabaseViewEditor {
|
|||||||
Ok(groups.into_iter().map(GroupPB::from).collect())
|
Ok(groups.into_iter().map(GroupPB::from).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "trace", skip(self))]
|
||||||
|
pub async fn v_get_group(&self, group_id: &str) -> FlowyResult<GroupPB> {
|
||||||
|
match self.group_controller.read().await.get_group(group_id) {
|
||||||
|
None => Err(FlowyError::record_not_found().context("Can't find the group")),
|
||||||
|
Some((_, group)) => Ok(GroupPB::from(group)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||||
pub async fn v_move_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
|
pub async fn v_move_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
|
||||||
self
|
self
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
#![allow(clippy::while_let_loop)]
|
#![allow(clippy::while_let_loop)]
|
||||||
use crate::entities::{
|
use crate::entities::{
|
||||||
AlterFilterParams, AlterSortParams, CreateRowParams, DatabaseViewSettingPB, DeleteFilterParams,
|
AlterFilterParams, AlterSortParams, CreateRowParams, DatabaseViewSettingPB, DeleteFilterParams,
|
||||||
DeleteGroupParams, DeleteSortParams, InsertGroupParams, MoveGroupParams, RepeatedGroupPB, RowPB,
|
DeleteGroupParams, DeleteSortParams, GroupPB, InsertGroupParams, MoveGroupParams,
|
||||||
|
RepeatedGroupPB, RowPB,
|
||||||
};
|
};
|
||||||
use crate::manager::DatabaseUser;
|
use crate::manager::DatabaseUser;
|
||||||
use crate::services::cell::AtomicCellDataCache;
|
use crate::services::cell::AtomicCellDataCache;
|
||||||
@ -201,6 +202,11 @@ impl DatabaseViews {
|
|||||||
Ok(RepeatedGroupPB { items: groups })
|
Ok(RepeatedGroupPB { items: groups })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_group(&self, view_id: &str, group_id: &str) -> FlowyResult<GroupPB> {
|
||||||
|
let view_editor = self.get_view_editor(view_id).await?;
|
||||||
|
view_editor.v_get_group(group_id).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn insert_or_update_group(&self, params: InsertGroupParams) -> FlowyResult<()> {
|
pub async fn insert_or_update_group(&self, params: InsertGroupParams) -> FlowyResult<()> {
|
||||||
let view_editor = self.get_view_editor(¶ms.view_id).await?;
|
let view_editor = self.get_view_editor(¶ms.view_id).await?;
|
||||||
view_editor.v_initialize_new_group(params).await
|
view_editor.v_initialize_new_group(params).await
|
||||||
|
Loading…
Reference in New Issue
Block a user