Merge remote-tracking branch 'origin/feat/appflowy_tauri_3' into feat/appflowy_tauri_3

This commit is contained in:
ascarbek
2023-03-01 18:41:32 +06:00
27 changed files with 392 additions and 225 deletions

View File

@ -1,3 +1,3 @@
fn main() { fn main() {
tauri_build::build() tauri_build::build()
} }

View File

@ -1,21 +1,22 @@
use flowy_core::{get_client_server_configuration, AppFlowyCore, AppFlowyCoreConfig}; use flowy_core::{get_client_server_configuration, AppFlowyCore, AppFlowyCoreConfig};
pub fn init_flowy_core() -> AppFlowyCore { pub fn init_flowy_core() -> AppFlowyCore {
let config_json = include_str!("../tauri.conf.json"); let config_json = include_str!("../tauri.conf.json");
let config: tauri_utils::config::Config = serde_json::from_str(config_json).unwrap(); let config: tauri_utils::config::Config = serde_json::from_str(config_json).unwrap();
let mut data_path = tauri::api::path::app_local_data_dir(&config).unwrap(); let mut data_path = tauri::api::path::app_local_data_dir(&config).unwrap();
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
data_path.push("dev"); data_path.push("dev");
} }
data_path.push("data"); data_path.push("data");
let server_config = get_client_server_configuration().unwrap(); std::env::set_var("RUST_LOG", "debug");
let config = AppFlowyCoreConfig::new( let server_config = get_client_server_configuration().unwrap();
data_path.to_str().unwrap(), let config = AppFlowyCoreConfig::new(
"AppFlowy".to_string(), data_path.to_str().unwrap(),
server_config, "AppFlowy".to_string(),
) server_config,
.log_filter("trace", vec!["appflowy_tauri".to_string()]); )
AppFlowyCore::new(config) .log_filter("trace", vec!["appflowy_tauri".to_string()]);
AppFlowyCore::new(config)
} }

View File

@ -32,7 +32,6 @@ impl std::convert::From<AFPluginEventResponse> for AFTauriResponse {
} }
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tracing::instrument(level = "trace", skip(app_handler))]
#[tauri::command] #[tauri::command]
pub async fn invoke_request( pub async fn invoke_request(
request: AFTauriRequest, request: AFTauriRequest,

View File

@ -60,11 +60,10 @@
"windows": [ "windows": [
{ {
"fullscreen": false, "fullscreen": false,
"height": 1000, "height": 1200,
"resizable": true, "resizable": true,
"title": "AppFlowy", "title": "AppFlowy",
"width": 1200, "width": 1200
"transparent": true
} }
] ]
} }

View File

@ -35,7 +35,7 @@ export async function assertTextCell(rowInfo: RowInfo, databaseController: Datab
onCellChanged: (value) => { onCellChanged: (value) => {
const cellContent = value.unwrap(); const cellContent = value.unwrap();
if (cellContent !== expectedContent) { if (cellContent !== expectedContent) {
throw Error(); throw Error('Text cell content is not match');
} }
}, },
}); });
@ -111,7 +111,7 @@ export async function assertFieldName(viewId: string, fieldId: string, fieldType
const svc = new TypeOptionBackendService(viewId); const svc = new TypeOptionBackendService(viewId);
const typeOptionPB = await svc.getTypeOption(fieldId, fieldType).then((result) => result.unwrap()); const typeOptionPB = await svc.getTypeOption(fieldId, fieldType).then((result) => result.unwrap());
if (typeOptionPB.field.name !== expected) { if (typeOptionPB.field.name !== expected) {
throw Error(); throw Error('Expect field name:' + expected + 'but receive:' + typeOptionPB.field.name);
} }
} }
@ -119,6 +119,14 @@ export async function assertNumberOfFields(viewId: string, expected: number) {
const svc = new DatabaseBackendService(viewId); const svc = new DatabaseBackendService(viewId);
const databasePB = await svc.openDatabase().then((result) => result.unwrap()); const databasePB = await svc.openDatabase().then((result) => result.unwrap());
if (databasePB.fields.length !== expected) { if (databasePB.fields.length !== expected) {
throw Error(); throw Error('Expect number of fields:' + expected + 'but receive:' + databasePB.fields.length);
}
}
export async function assertNumberOfRows(viewId: string, expected: number) {
const svc = new DatabaseBackendService(viewId);
const databasePB = await svc.openDatabase().then((result) => result.unwrap());
if (databasePB.rows.length !== expected) {
throw Error('Expect number of rows:' + expected + 'but receive:' + databasePB.rows.length);
} }
} }

View File

@ -3,10 +3,13 @@ import TestApiButton from './TestApiButton';
import { import {
TestCreateGrid, TestCreateGrid,
TestCreateNewField, TestCreateNewField,
TestCreateSelectOption, TestCreateRow,
TestCreateSelectOptionInCell,
TestDeleteField, TestDeleteField,
TestDeleteRow,
TestEditCell, TestEditCell,
TestEditField, TestEditField,
TestGetSingleSelectFieldData,
} from './TestGrid'; } from './TestGrid';
export const TestAPI = () => { export const TestAPI = () => {
@ -15,8 +18,11 @@ export const TestAPI = () => {
<ul className='m-6, space-y-2'> <ul className='m-6, space-y-2'>
<TestApiButton></TestApiButton> <TestApiButton></TestApiButton>
<TestCreateGrid></TestCreateGrid> <TestCreateGrid></TestCreateGrid>
<TestCreateRow></TestCreateRow>
<TestDeleteRow></TestDeleteRow>
<TestEditCell></TestEditCell> <TestEditCell></TestEditCell>
<TestCreateSelectOption></TestCreateSelectOption> <TestCreateSelectOptionInCell></TestCreateSelectOptionInCell>
<TestGetSingleSelectFieldData></TestGetSingleSelectFieldData>
<TestEditField></TestEditField> <TestEditField></TestEditField>
<TestCreateNewField></TestCreateNewField> <TestCreateNewField></TestCreateNewField>
<TestDeleteField></TestDeleteField> <TestDeleteField></TestDeleteField>

View File

@ -1,20 +1,29 @@
import React from 'react'; import React from 'react';
import { SelectOptionCellDataPB, ViewLayoutTypePB } from '../../../services/backend'; import {
FieldType,
SelectOptionCellDataPB,
SingleSelectTypeOptionPB,
ViewLayoutTypePB,
} from '../../../services/backend';
import { Log } from '../../utils/log'; import { Log } from '../../utils/log';
import { import {
assertFieldName, assertFieldName,
assertNumberOfFields, assertNumberOfFields,
assertNumberOfRows,
assertTextCell, assertTextCell,
createTestDatabaseView, createTestDatabaseView,
editTextCell, editTextCell,
makeSingleSelectCellController, makeSingleSelectCellController,
openTestDatabase, openTestDatabase,
} from './DatabaseTestHelper'; } from './DatabaseTestHelper';
import assert from 'assert'; import {
import { SelectOptionBackendService } from '../../stores/effects/database/cell/select_option_bd_svc'; SelectOptionBackendService,
SelectOptionCellBackendService,
} from '../../stores/effects/database/cell/select_option_bd_svc';
import { TypeOptionController } from '../../stores/effects/database/field/type_option/type_option_controller'; import { TypeOptionController } from '../../stores/effects/database/field/type_option/type_option_controller';
import { None, Some } from 'ts-results'; import { None, Some } from 'ts-results';
import { TypeOptionBackendService } from '../../stores/effects/database/field/type_option/type_option_bd_svc'; import { RowBackendService } from '../../stores/effects/database/row/row_bd_svc';
import { makeSingleSelectTypeOptionContext } from '../../stores/effects/database/field/type_option/type_option_context';
export const TestCreateGrid = () => { export const TestCreateGrid = () => {
async function createBuildInGrid() { async function createBuildInGrid() {
@ -26,16 +35,17 @@ export const TestCreateGrid = () => {
}, },
onRowsChanged: async (rows) => { onRowsChanged: async (rows) => {
if (rows.length !== 3) { if (rows.length !== 3) {
throw Error(); throw Error('Expected number of rows is 3, but receive ' + rows.length + view.id);
} }
}, },
onFieldsChanged: (fields) => { onFieldsChanged: (fields) => {
if (fields.length !== 3) { if (fields.length !== 3) {
throw Error(); throw Error('Expected number of fields is 3, but receive ' + fields.length);
} }
}, },
}); });
await databaseController.open().then((result) => result.unwrap()); await databaseController.open().then((result) => result.unwrap());
await databaseController.dispose();
} }
return TestButton('Test create build-in grid', createBuildInGrid); return TestButton('Test create build-in grid', createBuildInGrid);
@ -45,48 +55,116 @@ export const TestEditCell = () => {
async function testGridRow() { async function testGridRow() {
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid); const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
const databaseController = await openTestDatabase(view.id); const databaseController = await openTestDatabase(view.id);
databaseController.subscribe({
onRowsChanged: async (rows) => {
for (const [index, row] of rows.entries()) {
const cellContent = index.toString();
await editTextCell(row, databaseController, cellContent);
await assertTextCell(row, databaseController, cellContent);
}
},
});
await databaseController.open().then((result) => result.unwrap()); await databaseController.open().then((result) => result.unwrap());
for (const [index, row] of databaseController.databaseViewCache.rowInfos.entries()) {
const cellContent = index.toString();
await editTextCell(row, databaseController, cellContent);
await assertTextCell(row, databaseController, cellContent);
}
} }
return TestButton('Test editing cell', testGridRow); return TestButton('Test editing cell', testGridRow);
}; };
export const TestCreateSelectOption = () => { export const TestCreateRow = () => {
async function testCreateOption() { async function testCreateRow() {
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid); const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
const databaseController = await openTestDatabase(view.id); const databaseController = await openTestDatabase(view.id);
databaseController.subscribe({
onRowsChanged: async (rows) => {
for (const [index, row] of rows.entries()) {
if (index === 0) {
const cellController = await makeSingleSelectCellController(row, databaseController).then((result) =>
result.unwrap()
);
cellController.subscribeChanged({
onCellChanged: (value) => {
const option: SelectOptionCellDataPB = value.unwrap();
console.log(option);
},
});
const backendSvc = new SelectOptionBackendService(cellController.cellIdentifier);
await backendSvc.createOption({ name: 'option' + index });
}
}
},
});
await databaseController.open().then((result) => result.unwrap()); 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 a select option', testCreateOption); 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 cellController = await makeSingleSelectCellController(row, databaseController).then((result) =>
result.unwrap()
);
cellController.subscribeChanged({
onCellChanged: (value) => {
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 TestEditField = () => { export const TestEditField = () => {
@ -104,6 +182,7 @@ export const TestEditField = () => {
await controller.setFieldName(newName); await controller.setFieldName(newName);
await assertFieldName(view.id, firstFieldInfo.field.id, firstFieldInfo.field.field_type, newName); await assertFieldName(view.id, firstFieldInfo.field.id, firstFieldInfo.field.field_type, newName);
await databaseController.dispose();
} }
return TestButton('Test edit the column name', testEditField); return TestButton('Test edit the column name', testEditField);
@ -120,6 +199,7 @@ export const TestCreateNewField = () => {
const controller = new TypeOptionController(view.id, None); const controller = new TypeOptionController(view.id, None);
await controller.initialize(); await controller.initialize();
await assertNumberOfFields(view.id, 4); await assertNumberOfFields(view.id, 4);
await databaseController.dispose();
} }
return TestButton('Test create a new column', testCreateNewField); return TestButton('Test create a new column', testCreateNewField);
@ -140,6 +220,7 @@ export const TestDeleteField = () => {
await assertNumberOfFields(view.id, 3); await assertNumberOfFields(view.id, 3);
await controller.deleteField(); await controller.deleteField();
await assertNumberOfFields(view.id, 2); await assertNumberOfFields(view.id, 2);
await databaseController.dispose();
} }
return TestButton('Test delete a new column', testDeleteField); return TestButton('Test delete a new column', testDeleteField);

View File

@ -5,32 +5,37 @@ export class CellCacheKey {
constructor(public readonly fieldId: string, public readonly rowId: string) {} constructor(public readonly fieldId: string, public readonly rowId: string) {}
} }
type CellDataByRowId = Map<string, any>;
export class CellCache { export class CellCache {
_cellDataByFieldId = new Map<string, Map<string, any>>(); private cellDataByFieldId = new Map<string, CellDataByRowId>();
constructor(public readonly databaseId: string) {} constructor(public readonly databaseId: string) {}
remove = (key: CellCacheKey) => { remove = (key: CellCacheKey) => {
const inner = this._cellDataByFieldId.get(key.fieldId); const cellDataByRowId = this.cellDataByFieldId.get(key.fieldId);
if (inner !== undefined) { if (cellDataByRowId !== undefined) {
inner.delete(key.rowId); cellDataByRowId.delete(key.rowId);
} }
}; };
removeWithFieldId = (fieldId: string) => { removeWithFieldId = (fieldId: string) => {
this._cellDataByFieldId.delete(fieldId); this.cellDataByFieldId.delete(fieldId);
}; };
insert = (key: CellCacheKey, value: any) => { insert = (key: CellCacheKey, value: any) => {
let inner = this._cellDataByFieldId.get(key.fieldId); const cellDataByRowId = this.cellDataByFieldId.get(key.fieldId);
if (inner === undefined) { if (cellDataByRowId === undefined) {
inner = this._cellDataByFieldId.set(key.fieldId, new Map()); const map = new Map();
map.set(key.rowId, value);
this.cellDataByFieldId.set(key.fieldId, map);
} else {
cellDataByRowId.set(key.rowId, value);
} }
inner.set(key.rowId, value);
}; };
get<T>(key: CellCacheKey): Option<T> { get<T>(key: CellCacheKey): Option<T> {
const inner = this._cellDataByFieldId.get(key.fieldId); const inner = this.cellDataByFieldId.get(key.fieldId);
if (inner === undefined) { if (inner === undefined) {
return None; return None;
} else { } else {

View File

@ -14,10 +14,10 @@ export abstract class CellFieldNotifier {
} }
export class CellController<T, D> { export class CellController<T, D> {
private _fieldBackendService: FieldBackendService; private fieldBackendService: FieldBackendService;
private _cellDataNotifier: CellDataNotifier<Option<T>>; private cellDataNotifier: CellDataNotifier<Option<T>>;
private _cellObserver: CellObserver; private cellObserver: CellObserver;
private _cacheKey: CellCacheKey; private readonly cacheKey: CellCacheKey;
constructor( constructor(
public readonly cellIdentifier: CellIdentifier, public readonly cellIdentifier: CellIdentifier,
@ -26,17 +26,17 @@ export class CellController<T, D> {
private readonly cellDataLoader: CellDataLoader<T>, private readonly cellDataLoader: CellDataLoader<T>,
private readonly cellDataPersistence: CellDataPersistence<D> private readonly cellDataPersistence: CellDataPersistence<D>
) { ) {
this._fieldBackendService = new FieldBackendService(cellIdentifier.viewId, cellIdentifier.fieldId); this.fieldBackendService = new FieldBackendService(cellIdentifier.viewId, cellIdentifier.fieldId);
this._cacheKey = new CellCacheKey(cellIdentifier.rowId, cellIdentifier.fieldId); this.cacheKey = new CellCacheKey(cellIdentifier.rowId, cellIdentifier.fieldId);
this._cellDataNotifier = new CellDataNotifier(cellCache.get<T>(this._cacheKey)); this.cellDataNotifier = new CellDataNotifier(cellCache.get<T>(this.cacheKey));
this._cellObserver = new CellObserver(cellIdentifier.rowId, cellIdentifier.fieldId); this.cellObserver = new CellObserver(cellIdentifier.rowId, cellIdentifier.fieldId);
this._cellObserver.subscribe({ void this.cellObserver.subscribe({
/// 1.Listen on user edit event and load the new cell data if needed. /// 1.Listen on user edit event and load the new cell data if needed.
/// For example: /// For example:
/// user input: 12 /// user input: 12
/// cell display: $12 /// cell display: $12
onCellChanged: async () => { onCellChanged: async () => {
this.cellCache.remove(this._cacheKey); this.cellCache.remove(this.cacheKey);
await this._loadCellData(); await this._loadCellData();
}, },
}); });
@ -55,7 +55,7 @@ export class CellController<T, D> {
} }
}); });
this._cellDataNotifier.observer.subscribe((cellData) => { this.cellDataNotifier.observer.subscribe((cellData) => {
if (cellData !== null) { if (cellData !== null) {
callbacks.onCellChanged(cellData); callbacks.onCellChanged(cellData);
} }
@ -63,7 +63,7 @@ export class CellController<T, D> {
}; };
getTypeOption = async <P extends TypeOptionParser<PD>, PD>(parser: P) => { getTypeOption = async <P extends TypeOptionParser<PD>, PD>(parser: P) => {
const result = await this._fieldBackendService.getTypeOptionData(this.cellIdentifier.fieldType); const result = await this.fieldBackendService.getTypeOptionData(this.cellIdentifier.fieldType);
if (result.ok) { if (result.ok) {
return Ok(parser.fromBuffer(result.val.type_option_data)); return Ok(parser.fromBuffer(result.val.type_option_data));
} else { } else {
@ -83,7 +83,7 @@ export class CellController<T, D> {
/// data from the backend and then the [onCellChanged] will /// data from the backend and then the [onCellChanged] will
/// get called /// get called
getCellData = (): Option<T> => { getCellData = (): Option<T> => {
const cellData = this.cellCache.get<T>(this._cacheKey); const cellData = this.cellCache.get<T>(this.cacheKey);
if (cellData.none) { if (cellData.none) {
void this._loadCellData(); void this._loadCellData();
} }
@ -93,14 +93,18 @@ export class CellController<T, D> {
private _loadCellData = () => { private _loadCellData = () => {
return this.cellDataLoader.loadData().then((result) => { return this.cellDataLoader.loadData().then((result) => {
if (result.ok && result.val !== undefined) { if (result.ok && result.val !== undefined) {
this.cellCache.insert(this._cacheKey, result.val); this.cellCache.insert(this.cacheKey, result.val);
this._cellDataNotifier.cellData = Some(result.val); this.cellDataNotifier.cellData = Some(result.val);
} else { } else {
this.cellCache.remove(this._cacheKey); this.cellCache.remove(this.cacheKey);
this._cellDataNotifier.cellData = None; this.cellDataNotifier.cellData = None;
} }
}); });
}; };
dispose = async () => {
await this.cellObserver.unsubscribe();
};
} }
export class CellFieldNotifierImpl extends CellFieldNotifier { export class CellFieldNotifierImpl extends CellFieldNotifier {

View File

@ -13,7 +13,7 @@ export class CellObserver {
constructor(public readonly rowId: string, public readonly fieldId: string) {} constructor(public readonly rowId: string, public readonly fieldId: string) {}
subscribe = (callbacks: { onCellChanged: CellChangedCallback }) => { subscribe = async (callbacks: { onCellChanged: CellChangedCallback }) => {
this._notifier = new ChangeNotifier(); this._notifier = new ChangeNotifier();
this._notifier?.observer.subscribe(callbacks.onCellChanged); this._notifier?.observer.subscribe(callbacks.onCellChanged);
@ -33,7 +33,7 @@ export class CellObserver {
} }
}, },
}); });
return undefined; await this._listener.start();
}; };
unsubscribe = async () => { unsubscribe = async () => {

View File

@ -14,6 +14,20 @@ import {
} from '../../../../../services/backend/events/flowy-database'; } from '../../../../../services/backend/events/flowy-database';
export class SelectOptionBackendService { export class SelectOptionBackendService {
constructor(public readonly viewId: string, public readonly fieldId: string) {}
createOption = async (params: { name: string }) => {
const payload = CreateSelectOptionPayloadPB.fromObject({
option_name: params.name,
view_id: this.viewId,
field_id: this.fieldId,
});
return DatabaseEventCreateSelectOption(payload);
};
}
export class SelectOptionCellBackendService {
constructor(public readonly cellIdentifier: CellIdentifier) {} constructor(public readonly cellIdentifier: CellIdentifier) {}
createOption = async (params: { name: string; isSelect?: boolean }) => { createOption = async (params: { name: string; isSelect?: boolean }) => {
@ -31,6 +45,16 @@ export class SelectOptionBackendService {
} }
}; };
private _insertOption = (option: SelectOptionPB, isSelect: boolean) => {
const payload = SelectOptionChangesetPB.fromObject({ cell_identifier: this._cellIdentifier() });
if (isSelect) {
payload.insert_options.push(option);
} else {
payload.update_options.push(option);
}
return DatabaseEventUpdateSelectOption(payload);
};
updateOption = (option: SelectOptionPB) => { updateOption = (option: SelectOptionPB) => {
const payload = SelectOptionChangesetPB.fromObject({ cell_identifier: this._cellIdentifier() }); const payload = SelectOptionChangesetPB.fromObject({ cell_identifier: this._cellIdentifier() });
payload.update_options.push(option); payload.update_options.push(option);
@ -59,16 +83,6 @@ export class SelectOptionBackendService {
return DatabaseEventUpdateSelectOptionCell(payload); return DatabaseEventUpdateSelectOptionCell(payload);
}; };
private _insertOption = (option: SelectOptionPB, isSelect: boolean) => {
const payload = SelectOptionChangesetPB.fromObject({ cell_identifier: this._cellIdentifier() });
if (isSelect) {
payload.insert_options.push(option);
} else {
payload.update_options.push(option);
}
return DatabaseEventUpdateSelectOption(payload);
};
private _cellIdentifier = () => { private _cellIdentifier = () => {
return CellIdPB.fromObject({ return CellIdPB.fromObject({
view_id: this.cellIdentifier.viewId, view_id: this.cellIdentifier.viewId,

View File

@ -33,8 +33,7 @@ export class DatabaseBackendService {
}; };
createRow = async (rowId?: string) => { createRow = async (rowId?: string) => {
const props = { database_id: this.viewId, start_row_id: rowId ?? undefined }; const payload = CreateRowPayloadPB.fromObject({ view_id: this.viewId, start_row_id: rowId ?? undefined });
const payload = CreateRowPayloadPB.fromObject(props);
return DatabaseEventCreateRow(payload); return DatabaseEventCreateRow(payload);
}; };

View File

@ -12,13 +12,13 @@ export type SubscribeCallback = {
}; };
export class DatabaseController { export class DatabaseController {
private _backendService: DatabaseBackendService; private backendService: DatabaseBackendService;
fieldController: FieldController; fieldController: FieldController;
databaseViewCache: DatabaseViewCache; databaseViewCache: DatabaseViewCache;
private _callback?: SubscribeCallback; private _callback?: SubscribeCallback;
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);
} }
@ -32,11 +32,13 @@ export class DatabaseController {
}; };
open = async () => { open = async () => {
const result = await this._backendService.openDatabase(); const result = await this.backendService.openDatabase();
if (result.ok) { if (result.ok) {
const database: DatabasePB = result.val; const database: DatabasePB = result.val;
this._callback?.onViewChanged?.(database); this._callback?.onViewChanged?.(database);
await this.fieldController.loadFields(database.fields); await this.fieldController.loadFields(database.fields);
await this.databaseViewCache.listenOnRowsChanged();
await this.fieldController.listenOnFieldChanges();
this.databaseViewCache.initializeWithRows(database.rows); this.databaseViewCache.initializeWithRows(database.rows);
return Ok.EMPTY; return Ok.EMPTY;
} else { } else {
@ -45,11 +47,11 @@ export class DatabaseController {
}; };
createRow = async () => { createRow = async () => {
return this._backendService.createRow(); return this.backendService.createRow();
}; };
dispose = async () => { dispose = async () => {
await this._backendService.closeDatabase(); await this.backendService.closeDatabase();
await this.fieldController.dispose(); await this.fieldController.dispose();
await this.databaseViewCache.dispose(); await this.databaseViewCache.dispose();
}; };

View File

@ -5,47 +5,45 @@ import { FieldIdPB, FieldPB, IndexFieldPB } from '../../../../../services/backen
import { ChangeNotifier } from '../../../../utils/change_notifier'; import { ChangeNotifier } from '../../../../utils/change_notifier';
export class FieldController { export class FieldController {
private _fieldListener: DatabaseFieldChangesetObserver; private fieldListener: DatabaseFieldChangesetObserver;
private _backendService: DatabaseBackendService; private backendService: DatabaseBackendService;
private _fieldNotifier = new FieldNotifier([]); private fieldNotifier = new FieldNotifier([]);
constructor(public readonly viewId: string) { constructor(public readonly viewId: string) {
this._backendService = new DatabaseBackendService(viewId); this.backendService = new DatabaseBackendService(viewId);
this._fieldListener = new DatabaseFieldChangesetObserver(viewId); this.fieldListener = new DatabaseFieldChangesetObserver(viewId);
this._listenOnFieldChanges();
} }
dispose = async () => { dispose = async () => {
this._fieldNotifier.unsubscribe(); this.fieldNotifier.unsubscribe();
await this._fieldListener.unsubscribe(); await this.fieldListener.unsubscribe();
}; };
get fieldInfos(): readonly FieldInfo[] { get fieldInfos(): readonly FieldInfo[] {
return this._fieldNotifier.fieldInfos; return this.fieldNotifier.fieldInfos;
} }
getField = (fieldId: string): FieldInfo | undefined => { getField = (fieldId: string): FieldInfo | undefined => {
return this._fieldNotifier.fieldInfos.find((element) => element.field.id === fieldId); return this.fieldNotifier.fieldInfos.find((element) => element.field.id === fieldId);
}; };
loadFields = async (fieldIds: FieldIdPB[]) => { loadFields = async (fieldIds: FieldIdPB[]) => {
const result = await this._backendService.getFields(fieldIds); const result = await this.backendService.getFields(fieldIds);
if (result.ok) { if (result.ok) {
this._fieldNotifier.fieldInfos = result.val.map((field) => new FieldInfo(field)); this.fieldNotifier.fieldInfos = result.val.map((field) => new FieldInfo(field));
} else { } else {
Log.error(result.val); Log.error(result.val);
} }
}; };
subscribeOnFieldsChanged = (callback?: (fieldInfos: readonly FieldInfo[]) => void) => { subscribeOnFieldsChanged = (callback?: (fieldInfos: readonly FieldInfo[]) => void) => {
return this._fieldNotifier.observer.subscribe((fieldInfos) => { return this.fieldNotifier.observer.subscribe((fieldInfos) => {
callback?.(fieldInfos); callback?.(fieldInfos);
}); });
}; };
_listenOnFieldChanges = () => { listenOnFieldChanges = async () => {
this._fieldListener.subscribe({ await this.fieldListener.subscribe({
onFieldsChanged: (result) => { onFieldsChanged: (result) => {
if (result.ok) { if (result.ok) {
const changeset = result.val; const changeset = result.val;
@ -70,7 +68,7 @@ export class FieldController {
}; };
const newFieldInfos = [...this.fieldInfos]; const newFieldInfos = [...this.fieldInfos];
newFieldInfos.filter(predicate); newFieldInfos.filter(predicate);
this._fieldNotifier.fieldInfos = newFieldInfos; this.fieldNotifier.fieldInfos = newFieldInfos;
}; };
_insertFields = (insertedFields: IndexFieldPB[]) => { _insertFields = (insertedFields: IndexFieldPB[]) => {
@ -86,7 +84,7 @@ export class FieldController {
newFieldInfos.push(fieldInfo); newFieldInfos.push(fieldInfo);
} }
}); });
this._fieldNotifier.fieldInfos = newFieldInfos; this.fieldNotifier.fieldInfos = newFieldInfos;
}; };
_updateFields = (updatedFields: FieldPB[]) => { _updateFields = (updatedFields: FieldPB[]) => {
@ -104,7 +102,7 @@ export class FieldController {
} }
}); });
}); });
this._fieldNotifier.fieldInfos = newFieldInfos; this.fieldNotifier.fieldInfos = newFieldInfos;
}; };
} }

View File

@ -7,24 +7,24 @@ type UpdateFieldNotifiedValue = Result<DatabaseFieldChangesetPB, FlowyError>;
export type DatabaseNotificationCallback = (value: UpdateFieldNotifiedValue) => void; export type DatabaseNotificationCallback = (value: UpdateFieldNotifiedValue) => void;
export class DatabaseFieldChangesetObserver { export class DatabaseFieldChangesetObserver {
private _notifier?: ChangeNotifier<UpdateFieldNotifiedValue>; private notifier?: ChangeNotifier<UpdateFieldNotifiedValue>;
private _listener?: DatabaseNotificationObserver; private listener?: DatabaseNotificationObserver;
constructor(public readonly viewId: string) {} constructor(public readonly viewId: string) {}
subscribe = (callbacks: { onFieldsChanged: DatabaseNotificationCallback }) => { subscribe = async (callbacks: { onFieldsChanged: DatabaseNotificationCallback }) => {
this._notifier = new ChangeNotifier(); this.notifier = new ChangeNotifier();
this._notifier?.observer.subscribe(callbacks.onFieldsChanged); this.notifier?.observer.subscribe(callbacks.onFieldsChanged);
this._listener = new DatabaseNotificationObserver({ this.listener = new DatabaseNotificationObserver({
viewId: this.viewId, viewId: this.viewId,
parserHandler: (notification, result) => { parserHandler: (notification, result) => {
switch (notification) { switch (notification) {
case DatabaseNotification.DidUpdateFields: case DatabaseNotification.DidUpdateFields:
if (result.ok) { if (result.ok) {
this._notifier?.notify(Ok(DatabaseFieldChangesetPB.deserializeBinary(result.val))); this.notifier?.notify(Ok(DatabaseFieldChangesetPB.deserializeBinary(result.val)));
} else { } else {
this._notifier?.notify(result); this.notifier?.notify(result);
} }
return; return;
default: default:
@ -32,12 +32,12 @@ export class DatabaseFieldChangesetObserver {
} }
}, },
}); });
return undefined; await this.listener.start();
}; };
unsubscribe = async () => { unsubscribe = async () => {
this._notifier?.unsubscribe(); this.notifier?.unsubscribe();
await this._listener?.stop(); await this.listener?.stop();
}; };
} }
@ -50,7 +50,7 @@ export class DatabaseFieldObserver {
constructor(public readonly fieldId: string) {} constructor(public readonly fieldId: string) {}
subscribe = (callbacks: { onFieldsChanged: FieldNotificationCallback }) => { subscribe = async (callbacks: { onFieldsChanged: FieldNotificationCallback }) => {
this._notifier = new ChangeNotifier(); this._notifier = new ChangeNotifier();
this._notifier?.observer.subscribe(callbacks.onFieldsChanged); this._notifier?.observer.subscribe(callbacks.onFieldsChanged);
@ -70,7 +70,7 @@ export class DatabaseFieldObserver {
} }
}, },
}); });
return undefined; await this._listener.start();
}; };
unsubscribe = async () => { unsubscribe = async () => {

View File

@ -11,6 +11,7 @@ import {
URLTypeOptionPB, URLTypeOptionPB,
} from '../../../../../../services/backend'; } from '../../../../../../services/backend';
import { utf8Decoder, utf8Encoder } from '../../cell/data_parser'; import { utf8Decoder, utf8Encoder } from '../../cell/data_parser';
import { DatabaseFieldObserver } from '../field_observer';
abstract class TypeOptionSerde<T> { abstract class TypeOptionSerde<T> {
abstract deserialize(buffer: Uint8Array): T; abstract deserialize(buffer: Uint8Array): T;
@ -164,9 +165,17 @@ class ChecklistTypeOptionSerde extends TypeOptionSerde<ChecklistTypeOptionPB> {
export class TypeOptionContext<T> { export class TypeOptionContext<T> {
private typeOption: Option<T>; private typeOption: Option<T>;
private fieldObserver: DatabaseFieldObserver;
constructor(public readonly parser: TypeOptionSerde<T>, private readonly controller: TypeOptionController) { constructor(public readonly parser: TypeOptionSerde<T>, private readonly controller: TypeOptionController) {
this.typeOption = None; this.typeOption = None;
this.fieldObserver = new DatabaseFieldObserver(controller.fieldId);
void this.fieldObserver.subscribe({
onFieldsChanged: () => {
void this.getTypeOption();
},
});
} }
get viewId(): string { get viewId(): string {
@ -174,20 +183,23 @@ export class TypeOptionContext<T> {
} }
getTypeOption = async (): Promise<Result<T, FlowyError>> => { getTypeOption = async (): Promise<Result<T, FlowyError>> => {
if (this.typeOption.some) {
return Ok(this.typeOption.val);
}
const result = await this.controller.getTypeOption(); const result = await this.controller.getTypeOption();
if (result.ok) { if (result.ok) {
return Ok(this.parser.deserialize(result.val.type_option_data)); const typeOption = this.parser.deserialize(result.val.type_option_data);
this.typeOption = Some(typeOption);
return Ok(typeOption);
} else { } else {
return result; return result;
} }
}; };
setTypeOption = (typeOption: T) => { // Save the typeOption to disk
this.controller.typeOption = this.parser.serialize(typeOption); setTypeOption = async (typeOption: T) => {
await this.controller.saveTypeOption(this.parser.serialize(typeOption));
this.typeOption = Some(typeOption); this.typeOption = Some(typeOption);
}; };
dispose = async () => {
await this.fieldObserver.unsubscribe();
};
} }

View File

@ -59,10 +59,10 @@ export class TypeOptionController {
} }
}; };
set typeOption(data: Uint8Array) { saveTypeOption = async (data: Uint8Array) => {
if (this.typeOptionData.some) { if (this.typeOptionData.some) {
this.typeOptionData.val.type_option_data = data; this.typeOptionData.val.type_option_data = data;
void this.fieldBackendSvc?.updateTypeOption(data).then((result) => { await this.fieldBackendSvc?.updateTypeOption(data).then((result) => {
if (result.err) { if (result.err) {
Log.error(result.val); Log.error(result.val);
} }
@ -70,7 +70,7 @@ export class TypeOptionController {
} else { } else {
throw Error('Unexpect empty type option data. Should call initialize first'); throw Error('Unexpect empty type option data. Should call initialize first');
} }
} };
deleteField = async () => { deleteField = async () => {
if (this.fieldBackendSvc === undefined) { if (this.fieldBackendSvc === undefined) {

View File

@ -1,7 +1,6 @@
import { DatabaseNotification } from '../../../../../services/backend'; import { DatabaseNotification, FlowyError } from '../../../../../services/backend';
import { AFNotificationObserver } from '../../../../../services/backend/notifications'; import { AFNotificationObserver } from '../../../../../services/backend/notifications';
import { DatabaseNotificationParser } from './parser'; import { DatabaseNotificationParser } from './parser';
import { FlowyError } from '../../../../../services/backend';
import { Result } from 'ts-results'; import { Result } from 'ts-results';
export type ParserHandler = (notification: DatabaseNotification, result: Result<Uint8Array, FlowyError>) => void; export type ParserHandler = (notification: DatabaseNotification, result: Result<Uint8Array, FlowyError>) => void;

View File

@ -0,0 +1,32 @@
import { CreateRowPayloadPB, RowIdPB } from '../../../../../services/backend';
import {
DatabaseEventCreateRow,
DatabaseEventDeleteRow,
DatabaseEventDuplicateRow,
DatabaseEventGetRow,
} from '../../../../../services/backend/events/flowy-database';
export class RowBackendService {
constructor(public readonly viewId: string) {}
// Create a row below the row with rowId
createRow = (rowId: string) => {
const payload = CreateRowPayloadPB.fromObject({ view_id: this.viewId, start_row_id: rowId });
return DatabaseEventCreateRow(payload);
};
deleteRow = (rowId: string) => {
const payload = RowIdPB.fromObject({ view_id: this.viewId, row_id: rowId });
return DatabaseEventDeleteRow(payload);
};
duplicateRow = (rowId: string) => {
const payload = RowIdPB.fromObject({ view_id: this.viewId, row_id: rowId });
return DatabaseEventDuplicateRow(payload);
};
getRow = (rowId: string) => {
const payload = RowIdPB.fromObject({ view_id: this.viewId, row_id: rowId });
return DatabaseEventGetRow(payload);
};
}

View File

@ -1,63 +1,62 @@
import { DatabaseViewRowsObserver } from './view_row_observer'; import { DatabaseViewRowsObserver } from './view_row_observer';
import { RowCache, RowChangedReason, RowInfo } from '../row/row_cache'; import { RowCache, RowInfo } from '../row/row_cache';
import { FieldController } from '../field/field_controller'; import { FieldController } from '../field/field_controller';
import { RowPB } from '../../../../../services/backend/models/flowy-database/row_entities'; import { RowPB } from '../../../../../services/backend';
import { Subscription } from 'rxjs'; 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; 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.subscribeOnFieldsChanged((fieldInfos) => { this.fieldSubscription = fieldController.subscribeOnFieldsChanged((fieldInfos) => {
fieldInfos.forEach((fieldInfo) => { fieldInfos.forEach((fieldInfo) => {
this._rowCache.onFieldUpdated(fieldInfo); this.rowCache.onFieldUpdated(fieldInfo);
}); });
}); });
this._listenOnRowsChanged();
} }
initializeWithRows = (rows: RowPB[]) => { initializeWithRows = (rows: RowPB[]) => {
this._rowCache.initializeRows(rows); this.rowCache.initializeRows(rows);
}; };
get rowInfos(): readonly RowInfo[] { get rowInfos(): readonly RowInfo[] {
return this._rowCache.rows; return this.rowCache.rows;
} }
getRowCache = () => { getRowCache = () => {
return this._rowCache; return this.rowCache;
}; };
dispose = async () => { dispose = async () => {
this._fieldSubscription?.unsubscribe(); this.fieldSubscription?.unsubscribe();
await this._rowsObserver.unsubscribe(); await this.rowsObserver.unsubscribe();
await this._rowCache.dispose(); await this.rowCache.dispose();
}; };
_listenOnRowsChanged = () => { listenOnRowsChanged = async () => {
this._rowsObserver.subscribe({ await this.rowsObserver.subscribe({
onRowsVisibilityChanged: (result) => { onRowsVisibilityChanged: (result) => {
if (result.ok) { if (result.ok) {
this._rowCache.applyRowsVisibility(result.val); this.rowCache.applyRowsVisibility(result.val);
} }
}, },
onNumberOfRowsChanged: (result) => { onNumberOfRowsChanged: (result) => {
if (result.ok) { if (result.ok) {
this._rowCache.applyRowsChanged(result.val); this.rowCache.applyRowsChanged(result.val);
} }
}, },
onReorderRows: (result) => { onReorderRows: (result) => {
if (result.ok) { if (result.ok) {
this._rowCache.applyReorderRows(result.val); this.rowCache.applyReorderRows(result.val);
} }
}, },
onReorderSingleRow: (result) => { onReorderSingleRow: (result) => {
if (result.ok) { if (result.ok) {
this._rowCache.applyReorderSingleRow(result.val); this.rowCache.applyReorderSingleRow(result.val);
} }
}, },
}); });

View File

@ -16,26 +16,26 @@ export type ReorderRowsNotifyValue = Result<string[], FlowyError>;
export type ReorderSingleRowNotifyValue = Result<ReorderSingleRowPB, FlowyError>; export type ReorderSingleRowNotifyValue = Result<ReorderSingleRowPB, FlowyError>;
export class DatabaseViewRowsObserver { export class DatabaseViewRowsObserver {
private _rowsVisibilityNotifier = new ChangeNotifier<RowsVisibilityNotifyValue>(); private rowsVisibilityNotifier = new ChangeNotifier<RowsVisibilityNotifyValue>();
private _rowsNotifier = new ChangeNotifier<RowsNotifyValue>(); private rowsNotifier = new ChangeNotifier<RowsNotifyValue>();
private _reorderRowsNotifier = new ChangeNotifier<ReorderRowsNotifyValue>(); private reorderRowsNotifier = new ChangeNotifier<ReorderRowsNotifyValue>();
private _reorderSingleRowNotifier = new ChangeNotifier<ReorderSingleRowNotifyValue>(); private reorderSingleRowNotifier = new ChangeNotifier<ReorderSingleRowNotifyValue>();
private _listener?: DatabaseNotificationObserver; private _listener?: DatabaseNotificationObserver;
constructor(public readonly viewId: string) {} constructor(public readonly viewId: string) {}
subscribe = (callbacks: { subscribe = async (callbacks: {
onRowsVisibilityChanged?: (value: RowsVisibilityNotifyValue) => void; onRowsVisibilityChanged?: (value: RowsVisibilityNotifyValue) => void;
onNumberOfRowsChanged?: (value: RowsNotifyValue) => void; onNumberOfRowsChanged?: (value: RowsNotifyValue) => void;
onReorderRows?: (value: ReorderRowsNotifyValue) => void; onReorderRows?: (value: ReorderRowsNotifyValue) => void;
onReorderSingleRow?: (value: ReorderSingleRowNotifyValue) => void; onReorderSingleRow?: (value: ReorderSingleRowNotifyValue) => void;
}) => { }) => {
// //
this._rowsVisibilityNotifier.observer.subscribe(callbacks.onRowsVisibilityChanged); this.rowsVisibilityNotifier.observer.subscribe(callbacks.onRowsVisibilityChanged);
this._rowsNotifier.observer.subscribe(callbacks.onNumberOfRowsChanged); this.rowsNotifier.observer.subscribe(callbacks.onNumberOfRowsChanged);
this._reorderRowsNotifier.observer.subscribe(callbacks.onReorderRows); this.reorderRowsNotifier.observer.subscribe(callbacks.onReorderRows);
this._reorderSingleRowNotifier.observer.subscribe(callbacks.onReorderSingleRow); this.reorderSingleRowNotifier.observer.subscribe(callbacks.onReorderSingleRow);
this._listener = new DatabaseNotificationObserver({ this._listener = new DatabaseNotificationObserver({
viewId: this.viewId, viewId: this.viewId,
@ -43,30 +43,30 @@ export class DatabaseViewRowsObserver {
switch (notification) { switch (notification) {
case DatabaseNotification.DidUpdateViewRowsVisibility: case DatabaseNotification.DidUpdateViewRowsVisibility:
if (result.ok) { if (result.ok) {
this._rowsVisibilityNotifier.notify(Ok(RowsVisibilityChangesetPB.deserializeBinary(result.val))); this.rowsVisibilityNotifier.notify(Ok(RowsVisibilityChangesetPB.deserializeBinary(result.val)));
} else { } else {
this._rowsVisibilityNotifier.notify(result); this.rowsVisibilityNotifier.notify(result);
} }
break; break;
case DatabaseNotification.DidUpdateViewRows: case DatabaseNotification.DidUpdateViewRows:
if (result.ok) { if (result.ok) {
this._rowsNotifier.notify(Ok(RowsChangesetPB.deserializeBinary(result.val))); this.rowsNotifier.notify(Ok(RowsChangesetPB.deserializeBinary(result.val)));
} else { } else {
this._rowsNotifier.notify(result); this.rowsNotifier.notify(result);
} }
break; break;
case DatabaseNotification.DidReorderRows: case DatabaseNotification.DidReorderRows:
if (result.ok) { if (result.ok) {
this._reorderRowsNotifier.notify(Ok(ReorderAllRowsPB.deserializeBinary(result.val).row_orders)); this.reorderRowsNotifier.notify(Ok(ReorderAllRowsPB.deserializeBinary(result.val).row_orders));
} else { } else {
this._reorderRowsNotifier.notify(result); this.reorderRowsNotifier.notify(result);
} }
break; break;
case DatabaseNotification.DidReorderSingleRow: case DatabaseNotification.DidReorderSingleRow:
if (result.ok) { if (result.ok) {
this._reorderSingleRowNotifier.notify(Ok(ReorderSingleRowPB.deserializeBinary(result.val))); this.reorderSingleRowNotifier.notify(Ok(ReorderSingleRowPB.deserializeBinary(result.val)));
} else { } else {
this._reorderSingleRowNotifier.notify(result); this.reorderSingleRowNotifier.notify(result);
} }
break; break;
default: default:
@ -74,13 +74,14 @@ export class DatabaseViewRowsObserver {
} }
}, },
}); });
await this._listener.start();
}; };
unsubscribe = async () => { unsubscribe = async () => {
this._rowsVisibilityNotifier.unsubscribe(); this.rowsVisibilityNotifier.unsubscribe();
this._reorderRowsNotifier.unsubscribe(); this.reorderRowsNotifier.unsubscribe();
this._rowsNotifier.unsubscribe(); this.rowsNotifier.unsubscribe();
this._reorderSingleRowNotifier.unsubscribe(); this.reorderSingleRowNotifier.unsubscribe();
await this._listener?.stop(); await this._listener?.stop();
}; };
} }

View File

@ -6,15 +6,14 @@ import { FolderNotificationObserver } from '../notifications/observer';
export type AppUpdateNotifyValue = Result<AppPB, FlowyError>; export type AppUpdateNotifyValue = Result<AppPB, FlowyError>;
export type AppUpdateNotifyCallback = (value: AppUpdateNotifyValue) => void; export type AppUpdateNotifyCallback = (value: AppUpdateNotifyValue) => void;
export class WorkspaceObserver { export class AppObserver {
_appNotifier = new ChangeNotifier<AppUpdateNotifyValue>(); _appNotifier = new ChangeNotifier<AppUpdateNotifyValue>();
_listener?: FolderNotificationObserver; _listener?: FolderNotificationObserver;
constructor(public readonly appId: string) {} constructor(public readonly appId: string) {}
subscribe = (callbacks: { onAppChanged: AppUpdateNotifyCallback }) => { subscribe = async (callbacks: { onAppChanged: AppUpdateNotifyCallback }) => {
this._appNotifier?.observer.subscribe(callbacks.onAppChanged); this._appNotifier?.observer.subscribe(callbacks.onAppChanged);
this._listener = new FolderNotificationObserver({ this._listener = new FolderNotificationObserver({
viewId: this.appId, viewId: this.appId,
parserHandler: (notification, result) => { parserHandler: (notification, result) => {
@ -31,7 +30,7 @@ export class WorkspaceObserver {
} }
}, },
}); });
return undefined; await this._listener.start();
}; };
unsubscribe = async () => { unsubscribe = async () => {

View File

@ -18,7 +18,7 @@ export class ViewObserver {
constructor(public readonly viewId: string) {} constructor(public readonly viewId: string) {}
subscribe = (callbacks: { subscribe = async (callbacks: {
onViewUpdate?: (value: UpdateViewNotifyValue) => void; onViewUpdate?: (value: UpdateViewNotifyValue) => void;
onViewDelete?: (value: DeleteViewNotifyValue) => void; onViewDelete?: (value: DeleteViewNotifyValue) => void;
onViewRestored?: (value: RestoreViewNotifyValue) => void; onViewRestored?: (value: RestoreViewNotifyValue) => void;
@ -77,7 +77,7 @@ export class ViewObserver {
} }
}, },
}); });
return undefined; await this._listener.start();
}; };
unsubscribe = async () => { unsubscribe = async () => {

View File

@ -9,32 +9,35 @@ export type WorkspaceNotifyValue = Result<WorkspacePB, FlowyError>;
export type WorkspaceNotifyCallback = (value: WorkspaceNotifyValue) => void; export type WorkspaceNotifyCallback = (value: WorkspaceNotifyValue) => void;
export class WorkspaceObserver { export class WorkspaceObserver {
private _appListNotifier = new ChangeNotifier<AppListNotifyValue>(); private appListNotifier = new ChangeNotifier<AppListNotifyValue>();
private _workspaceNotifier = new ChangeNotifier<WorkspaceNotifyValue>(); private workspaceNotifier = new ChangeNotifier<WorkspaceNotifyValue>();
private _listener?: FolderNotificationObserver; private listener?: FolderNotificationObserver;
constructor(public readonly workspaceId: string) {} constructor(public readonly workspaceId: string) {}
subscribe = (callbacks: { onAppListChanged: AppListNotifyCallback; onWorkspaceChanged: WorkspaceNotifyCallback }) => { subscribe = async (callbacks: {
this._appListNotifier?.observer.subscribe(callbacks.onAppListChanged); onAppListChanged: AppListNotifyCallback;
this._workspaceNotifier?.observer.subscribe(callbacks.onWorkspaceChanged); onWorkspaceChanged: WorkspaceNotifyCallback;
}) => {
this.appListNotifier?.observer.subscribe(callbacks.onAppListChanged);
this.workspaceNotifier?.observer.subscribe(callbacks.onWorkspaceChanged);
this._listener = new FolderNotificationObserver({ this.listener = new FolderNotificationObserver({
viewId: this.workspaceId, viewId: this.workspaceId,
parserHandler: (notification, result) => { parserHandler: (notification, result) => {
switch (notification) { switch (notification) {
case FolderNotification.DidUpdateWorkspace: case FolderNotification.DidUpdateWorkspace:
if (result.ok) { if (result.ok) {
this._workspaceNotifier?.notify(Ok(WorkspacePB.deserializeBinary(result.val))); this.workspaceNotifier?.notify(Ok(WorkspacePB.deserializeBinary(result.val)));
} else { } else {
this._workspaceNotifier?.notify(result); this.workspaceNotifier?.notify(result);
} }
break; break;
case FolderNotification.DidUpdateWorkspaceApps: case FolderNotification.DidUpdateWorkspaceApps:
if (result.ok) { if (result.ok) {
this._appListNotifier?.notify(Ok(RepeatedAppPB.deserializeBinary(result.val).items)); this.appListNotifier?.notify(Ok(RepeatedAppPB.deserializeBinary(result.val).items));
} else { } else {
this._appListNotifier?.notify(result); this.appListNotifier?.notify(result);
} }
break; break;
default: default:
@ -42,12 +45,12 @@ export class WorkspaceObserver {
} }
}, },
}); });
return undefined; await this.listener.start();
}; };
unsubscribe = async () => { unsubscribe = async () => {
this._appListNotifier.unsubscribe(); this.appListNotifier.unsubscribe();
this._workspaceNotifier.unsubscribe(); this.workspaceNotifier.unsubscribe();
await this._listener?.stop(); await this.listener?.stop();
}; };
} }

View File

@ -1,6 +1,6 @@
import { listen, UnlistenFn } from '@tauri-apps/api/event'; import { listen, UnlistenFn } from "@tauri-apps/api/event";
import { SubscribeObject } from '../models/flowy-notification'; import { SubscribeObject } from "../models/flowy-notification";
import { NotificationParser } from './parser'; import { NotificationParser } from "./parser";
export abstract class AFNotificationObserver<T> { export abstract class AFNotificationObserver<T> {
parser?: NotificationParser<T> | null; parser?: NotificationParser<T> | null;
@ -11,9 +11,15 @@ export abstract class AFNotificationObserver<T> {
} }
async start() { async start() {
this._listener = await listen('af-notification', (notification) => { this._listener = await listen("af-notification", (notification) => {
const object = SubscribeObject.fromObject(notification.payload as {}); const object: SubscribeObject = SubscribeObject.fromObject(notification.payload as {});
this.parser?.parse(object); if (this.parser?.id !== undefined) {
if (object.id === this.parser.id) {
this.parser?.parse(object);
}
} else {
this.parser?.parse(object);
}
}); });
} }

View File

@ -24,14 +24,14 @@ export async function {{ event_func_name }}(): Promise<Result<{{ output_deserial
if (result.code == 0) { if (result.code == 0) {
{%- if has_output %} {%- if has_output %}
let object = {{ output_deserializer }}.deserializeBinary(result.payload); let object = {{ output_deserializer }}.deserializeBinary(result.payload);
console.log("Success:" + JSON.stringify(object.toObject())) console.log({{ event_func_name }}.name, object);
return Ok(object); return Ok(object);
{%- else %} {%- else %}
return Ok.EMPTY; return Ok.EMPTY;
{%- endif %} {%- endif %}
} else { } else {
let error = {{ error_deserializer }}.deserializeBinary(result.payload); let error = {{ error_deserializer }}.deserializeBinary(result.payload);
console.log("Error:" + JSON.stringify(error.toObject())) console.log({{ event_func_name }}.name, error);
return Err(error); return Err(error);
} }
} }

View File

@ -191,7 +191,7 @@ impl TryInto<CreateRowParams> for CreateRowPayloadPB {
type Error = ErrorCode; type Error = ErrorCode;
fn try_into(self) -> Result<CreateRowParams, Self::Error> { fn try_into(self) -> Result<CreateRowParams, Self::Error> {
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?;
Ok(CreateRowParams { Ok(CreateRowParams {
view_id: view_id.0, view_id: view_id.0,