Skip to content

Commit 90da54d

Browse files
appflowyAscarbek
andauthored
feat: integrate database controller (tauri)
* feat: using controllers in react hooks WIP (#1915) * chore: add edit / create field test * chore: add delete field test * chore: change log class arguments * chore: delete/create row * chore: set tracing log to debug level * fix: filter notification with id * chore: add get single select type option data * fix: high cpu usage * chore: format code * chore: update tokio version * chore: config tokio runtime subscriber * chore: add profiling feature * chore: setup auto login * chore: fix tauri build * chore: (unstable) using controllers * fix: initially authenticated and serializable fix * fix: ci warning * ci: compile error * fix: new folder trash overflow * fix: min width for nav panel * fix: nav panel and main panel animation on hide menu * fix: highlight active page * fix: post merge fixes * fix: post merge fix * fix: remove warnings * fix: change IDatabaseField fix eslint errors * chore: create cell component for each field type * chore: move cell hook into custom cell component * chore: refactor row hook * chore: add tauri clean --------- Co-authored-by: nathan <[email protected]> Co-authored-by: Nathan.fooo <[email protected]> * ci: fix wanrings --------- Co-authored-by: Askarbek Zadauly <[email protected]>
1 parent e73870e commit 90da54d

File tree

34 files changed

+616
-342
lines changed

34 files changed

+616
-342
lines changed

frontend/appflowy_tauri/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
"preview": "vite preview",
1010
"format": "prettier --write .",
1111
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
12+
"test:errors": "eslint --quiet --ext .js,.ts,.tsx .",
1213
"test:prettier": "yarn prettier --list-different src",
14+
"tauri:clean": "cargo make --cwd .. tauri_clean",
1315
"tauri:dev": "tauri dev",
1416
"test": "jest"
1517
},

frontend/appflowy_tauri/src/appflowy_app/components/_shared/Database.hooks.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const useDatabase = () => {
99
const database = useAppSelector((state) => state.database);
1010

1111
const newField = () => {
12-
dispatch(
12+
/* dispatch(
1313
databaseActions.addField({
1414
field: {
1515
fieldId: nanoid(8),
@@ -18,22 +18,25 @@ export const useDatabase = () => {
1818
title: 'new field',
1919
},
2020
})
21-
);
21+
);*/
22+
console.log('depreciated');
2223
};
2324

2425
const renameField = (fieldId: string, newTitle: string) => {
25-
const field = database.fields[fieldId];
26+
/* const field = database.fields[fieldId];
2627
field.title = newTitle;
2728
2829
dispatch(
2930
databaseActions.updateField({
3031
field,
3132
})
32-
);
33+
);*/
34+
console.log('depreciated');
3335
};
3436

3537
const newRow = () => {
36-
dispatch(databaseActions.addRow());
38+
// dispatch(databaseActions.addRow());
39+
console.log('depreciated');
3740
};
3841

3942
return {
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { TypeOptionController } from '../../../stores/effects/database/field/type_option/type_option_controller';
2+
import { Some } from 'ts-results';
3+
import { IDatabaseField, ISelectOption } from '../../../stores/reducers/database/slice';
4+
import {
5+
ChecklistTypeOptionPB,
6+
DateFormat,
7+
FieldType,
8+
MultiSelectTypeOptionPB,
9+
NumberFormat,
10+
SingleSelectTypeOptionPB,
11+
TimeFormat,
12+
} from '../../../../services/backend';
13+
import {
14+
makeChecklistTypeOptionContext,
15+
makeDateTypeOptionContext,
16+
makeMultiSelectTypeOptionContext,
17+
makeNumberTypeOptionContext,
18+
makeSingleSelectTypeOptionContext,
19+
} from '../../../stores/effects/database/field/type_option/type_option_context';
20+
import { boardActions } from '../../../stores/reducers/board/slice';
21+
import { FieldInfo } from '../../../stores/effects/database/field/field_controller';
22+
import { AppDispatch } from '../../../stores/store';
23+
24+
export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?: AppDispatch): Promise<IDatabaseField> {
25+
const field = fieldInfo.field;
26+
const typeOptionController = new TypeOptionController(viewId, Some(fieldInfo));
27+
28+
// temporary hack to set grouping field
29+
let groupingFieldSelected = false;
30+
31+
switch (field.field_type) {
32+
case FieldType.SingleSelect:
33+
case FieldType.MultiSelect:
34+
case FieldType.Checklist: {
35+
let selectOptions: ISelectOption[] = [];
36+
let typeOption: SingleSelectTypeOptionPB | MultiSelectTypeOptionPB | ChecklistTypeOptionPB | undefined;
37+
38+
if (field.field_type === FieldType.SingleSelect) {
39+
typeOption = (await makeSingleSelectTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
40+
if (!groupingFieldSelected) {
41+
if (dispatch) {
42+
dispatch(boardActions.setGroupingFieldId({ fieldId: field.id }));
43+
}
44+
groupingFieldSelected = true;
45+
}
46+
}
47+
if (field.field_type === FieldType.MultiSelect) {
48+
typeOption = (await makeMultiSelectTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
49+
}
50+
if (field.field_type === FieldType.Checklist) {
51+
typeOption = (await makeChecklistTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
52+
}
53+
54+
if (typeOption) {
55+
selectOptions = typeOption.options.map<ISelectOption>((option) => {
56+
return {
57+
selectOptionId: option.id,
58+
title: option.name,
59+
color: option.color,
60+
};
61+
});
62+
}
63+
64+
return {
65+
fieldId: field.id,
66+
title: field.name,
67+
fieldType: field.field_type,
68+
fieldOptions: {
69+
selectOptions,
70+
},
71+
};
72+
}
73+
74+
case FieldType.Number: {
75+
const typeOption = (await makeNumberTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
76+
return {
77+
fieldId: field.id,
78+
title: field.name,
79+
fieldType: field.field_type,
80+
fieldOptions: {
81+
numberFormat: typeOption.format,
82+
},
83+
};
84+
}
85+
86+
case FieldType.DateTime: {
87+
const typeOption = (await makeDateTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
88+
return {
89+
fieldId: field.id,
90+
title: field.name,
91+
fieldType: field.field_type,
92+
fieldOptions: {
93+
dateFormat: typeOption.date_format,
94+
timeFormat: typeOption.time_format,
95+
includeTime: typeOption.include_time,
96+
},
97+
};
98+
}
99+
100+
default: {
101+
return {
102+
fieldId: field.id,
103+
title: field.name,
104+
fieldType: field.field_type,
105+
};
106+
}
107+
}
108+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { CellIdentifier } from '../../../stores/effects/database/cell/cell_bd_svc';
2+
import { CellCache } from '../../../stores/effects/database/cell/cell_cache';
3+
import { FieldController } from '../../../stores/effects/database/field/field_controller';
4+
import { CellControllerBuilder } from '../../../stores/effects/database/cell/controller_builder';
5+
import { DateCellDataPB, SelectOptionCellDataPB, URLCellDataPB } from '../../../../services/backend';
6+
import { useEffect, useState } from 'react';
7+
8+
export const useCell = (cellIdentifier: CellIdentifier, cellCache: CellCache, fieldController: FieldController) => {
9+
const [data, setData] = useState<DateCellDataPB | URLCellDataPB | SelectOptionCellDataPB | string | undefined>();
10+
11+
useEffect(() => {
12+
const builder = new CellControllerBuilder(cellIdentifier, cellCache, fieldController);
13+
const cellController = builder.build();
14+
cellController.subscribeChanged({
15+
onCellChanged: (value) => {
16+
setData(value.unwrap());
17+
},
18+
});
19+
20+
// ignore the return value, because we are using the subscription
21+
void cellController.getCellData();
22+
23+
// dispose the cell controller when the component is unmounted
24+
return () => {
25+
void cellController.dispose();
26+
};
27+
}, []);
28+
29+
return {
30+
data,
31+
};
32+
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { useEffect, useState } from 'react';
2+
import { DatabaseController } from '../../../stores/effects/database/database_controller';
3+
import {
4+
databaseActions,
5+
DatabaseFieldMap,
6+
IDatabaseColumn,
7+
IDatabaseRow,
8+
} from '../../../stores/reducers/database/slice';
9+
import { useAppDispatch, useAppSelector } from '../../../stores/store';
10+
import loadField from './loadField';
11+
import { FieldInfo } from '../../../stores/effects/database/field/field_controller';
12+
import { RowInfo } from '../../../stores/effects/database/row/row_cache';
13+
14+
export const useDatabase = (viewId: string) => {
15+
const dispatch = useAppDispatch();
16+
const databaseStore = useAppSelector((state) => state.database);
17+
const boardStore = useAppSelector((state) => state.board);
18+
const [controller, setController] = useState<DatabaseController>();
19+
const [rows, setRows] = useState<readonly RowInfo[]>([]);
20+
21+
useEffect(() => {
22+
if (!viewId.length) return;
23+
const c = new DatabaseController(viewId);
24+
setController(c);
25+
26+
// on unmount dispose the controller
27+
return () => void c.dispose();
28+
}, [viewId]);
29+
30+
const loadFields = async (fieldInfos: readonly FieldInfo[]) => {
31+
const fields: DatabaseFieldMap = {};
32+
const columns: IDatabaseColumn[] = [];
33+
34+
for (const fieldInfo of fieldInfos) {
35+
const fieldPB = fieldInfo.field;
36+
columns.push({
37+
fieldId: fieldPB.id,
38+
sort: 'none',
39+
visible: fieldPB.visibility,
40+
});
41+
42+
const field = await loadField(viewId, fieldInfo, dispatch);
43+
fields[field.fieldId] = field;
44+
}
45+
46+
dispatch(databaseActions.updateFields({ fields }));
47+
dispatch(databaseActions.updateColumns({ columns }));
48+
console.log(fields, columns);
49+
};
50+
51+
useEffect(() => {
52+
if (!controller) return;
53+
54+
void (async () => {
55+
controller.subscribe({
56+
onRowsChanged: (rowInfos) => {
57+
setRows(rowInfos);
58+
},
59+
onFieldsChanged: (fieldInfos) => {
60+
void loadFields(fieldInfos);
61+
},
62+
});
63+
await controller.open();
64+
})();
65+
}, [controller]);
66+
67+
return { loadFields, controller, rows };
68+
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { DatabaseController } from '../../../stores/effects/database/database_controller';
2+
import { RowController } from '../../../stores/effects/database/row/row_controller';
3+
import { RowInfo } from '../../../stores/effects/database/row/row_cache';
4+
import { CellIdentifier } from '../../../stores/effects/database/cell/cell_bd_svc';
5+
import { useEffect, useState } from 'react';
6+
7+
export const useRow = (viewId: string, databaseController: DatabaseController, rowInfo: RowInfo) => {
8+
const [cells, setCells] = useState<{ fieldId: string; cellIdentifier: CellIdentifier }[]>([]);
9+
const [rowController, setRowController] = useState<RowController>();
10+
11+
useEffect(() => {
12+
const rowCache = databaseController.databaseViewCache.getRowCache();
13+
const fieldController = databaseController.fieldController;
14+
const c = new RowController(rowInfo, fieldController, rowCache);
15+
setRowController(c);
16+
17+
return () => {
18+
// dispose row controller in future
19+
};
20+
}, []);
21+
22+
useEffect(() => {
23+
if (!rowController) return;
24+
25+
void (async () => {
26+
const cellsPB = await rowController.loadCells();
27+
const loadingCells: { fieldId: string; cellIdentifier: CellIdentifier }[] = [];
28+
29+
for (const [fieldId, cellIdentifier] of cellsPB.entries()) {
30+
loadingCells.push({
31+
fieldId,
32+
cellIdentifier,
33+
});
34+
}
35+
36+
setCells(loadingCells);
37+
})();
38+
}, [rowController]);
39+
40+
return {
41+
cells: cells,
42+
};
43+
};

frontend/appflowy_tauri/src/appflowy_app/components/auth/ProtectedRoutes.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useEffect, useState } from 'react';
55
import { GetStarted } from './GetStarted/GetStarted';
66
import { AppflowyLogo } from '../_shared/svg/AppflowyLogo';
77

8+
89
export const ProtectedRoutes = () => {
910
const { currentUser, checkUser } = useAuth();
1011
const [isLoading, setIsLoading] = useState(true);

frontend/appflowy_tauri/src/appflowy_app/components/board/Board.hooks.ts

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,24 @@
11
import { useEffect, useState } from 'react';
22
import { useAppDispatch, useAppSelector } from '../../stores/store';
33
import { boardActions } from '../../stores/reducers/board/slice';
4-
import { ICellData, IDatabase, IDatabaseRow, ISelectOption } from '../../stores/reducers/database/slice';
4+
import { ISelectOption, ISelectOptionType } from '../../stores/reducers/database/slice';
55

66
export const useBoard = () => {
77
const dispatch = useAppDispatch();
88
const groupingFieldId = useAppSelector((state) => state.board);
99
const database = useAppSelector((state) => state.database);
1010
const [title, setTitle] = useState('');
11-
const [boardColumns, setBoardColumns] =
12-
useState<(ISelectOption & { rows: (IDatabaseRow & { isGhost: boolean })[] })[]>();
11+
const [boardColumns, setBoardColumns] = useState<ISelectOption[]>([]);
1312
const [movingRowId, setMovingRowId] = useState<string | undefined>(undefined);
1413
const [ghostLocation, setGhostLocation] = useState<{ column: number; row: number }>({ column: 0, row: 0 });
1514

1615
useEffect(() => {
1716
setTitle(database.title);
18-
setBoardColumns(
19-
database.fields[groupingFieldId].fieldOptions.selectOptions?.map((groupFieldItem) => {
20-
const rows = database.rows
21-
.filter((row) => row.cells[groupingFieldId].optionIds?.some((so) => so === groupFieldItem.selectOptionId))
22-
.map((row) => ({
23-
...row,
24-
isGhost: false,
25-
}));
26-
return {
27-
...groupFieldItem,
28-
rows: rows,
29-
};
30-
}) || []
31-
);
17+
if (database.fields[groupingFieldId]) {
18+
setBoardColumns(
19+
(database.fields[groupingFieldId].fieldOptions as ISelectOptionType | undefined)?.selectOptions || []
20+
);
21+
}
3222
}, [database, groupingFieldId]);
3323

3424
const changeGroupingField = (fieldId: string) => {

frontend/appflowy_tauri/src/appflowy_app/components/board/Board.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { SettingsSvg } from '../_shared/svg/SettingsSvg';
22
import { SearchInput } from '../_shared/SearchInput';
3-
import { useDatabase } from '../_shared/Database.hooks';
43
import { BoardBlock } from './BoardBlock';
54
import { NewBoardBlock } from './NewBoardBlock';
6-
import { IDatabaseRow } from '../../stores/reducers/database/slice';
75
import { useBoard } from './Board.hooks';
6+
import { useDatabase } from '../_shared/database-hooks/useDatabase';
7+
8+
export const Board = ({ viewId }: { viewId: string }) => {
9+
const { controller, rows } = useDatabase(viewId);
810

9-
export const Board = () => {
10-
const { database, newField, renameField, newRow } = useDatabase();
1111
const {
1212
title,
1313
boardColumns,
@@ -36,16 +36,15 @@ export const Board = () => {
3636
</div>
3737
<div className={'relative w-full flex-1 overflow-auto'}>
3838
<div className={'absolute flex h-full flex-shrink-0 items-start justify-start gap-4'}>
39-
{database &&
39+
{controller &&
4040
boardColumns?.map((column, index) => (
4141
<BoardBlock
42+
viewId={viewId}
43+
controller={controller}
4244
key={index}
4345
title={column.title}
46+
rows={rows}
4447
groupingFieldId={groupingFieldId}
45-
count={column.rows.length}
46-
fields={database.fields}
47-
columns={database.columns}
48-
rows={column.rows}
4948
startMove={startMove}
5049
endMove={endMove}
5150
/>

0 commit comments

Comments
 (0)