Browse Source

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]>
Nathan.fooo 2 years ago
parent
commit
90da54d12f
34 changed files with 616 additions and 342 deletions
  1. 2 0
      frontend/appflowy_tauri/package.json
  2. 8 5
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/Database.hooks.ts
  3. 108 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/loadField.ts
  4. 32 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useCell.ts
  5. 68 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useDatabase.ts
  6. 43 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useRow.ts
  7. 1 0
      frontend/appflowy_tauri/src/appflowy_app/components/auth/ProtectedRoutes.tsx
  8. 7 17
      frontend/appflowy_tauri/src/appflowy_app/components/board/Board.hooks.ts
  9. 8 9
      frontend/appflowy_tauri/src/appflowy_app/components/board/Board.tsx
  10. 14 15
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardBlock.tsx
  11. 27 31
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCard.tsx
  12. 43 0
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCell.tsx
  13. 18 0
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardDateCell.tsx
  14. 25 0
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardOptionsCell.tsx
  15. 18 0
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardTextCell.tsx
  16. 9 1
      frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/Breadcrumbs.tsx
  17. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/HeaderPanel.tsx
  18. 33 4
      frontend/appflowy_tauri/src/appflowy_app/components/layout/MainPanel.tsx
  19. 17 1
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.hooks.ts
  20. 18 6
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx
  21. 6 2
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationResizer.tsx
  22. 3 1
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.hooks.ts
  23. 4 1
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.tsx
  24. 14 19
      frontend/appflowy_tauri/src/appflowy_app/components/layout/Screen.tsx
  25. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/tests/DatabaseTestHelper.ts
  26. 1 1
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/cell_controller.ts
  27. 1 2
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/folder/notifications/observer.ts
  28. 12 0
      frontend/appflowy_tauri/src/appflowy_app/stores/reducers/activePageId/slice.ts
  29. 1 1
      frontend/appflowy_tauri/src/appflowy_app/stores/reducers/board/slice.ts
  30. 55 206
      frontend/appflowy_tauri/src/appflowy_app/stores/reducers/database/slice.ts
  31. 2 0
      frontend/appflowy_tauri/src/appflowy_app/stores/store.ts
  32. 10 11
      frontend/appflowy_tauri/src/appflowy_app/utils/log.ts
  33. 5 5
      frontend/appflowy_tauri/src/appflowy_app/views/BoardPage.tsx
  34. 0 1
      frontend/rust-lib/flowy-core/src/lib.rs

+ 2 - 0
frontend/appflowy_tauri/package.json

@@ -9,7 +9,9 @@
     "preview": "vite preview",
     "format": "prettier --write .",
     "test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
+    "test:errors": "eslint --quiet --ext .js,.ts,.tsx .",
     "test:prettier": "yarn prettier --list-different src",
+    "tauri:clean": "cargo make --cwd .. tauri_clean",
     "tauri:dev": "tauri dev",
     "test": "jest"
   },

+ 8 - 5
frontend/appflowy_tauri/src/appflowy_app/components/_shared/Database.hooks.ts

@@ -9,7 +9,7 @@ export const useDatabase = () => {
   const database = useAppSelector((state) => state.database);
 
   const newField = () => {
-    dispatch(
+    /* dispatch(
       databaseActions.addField({
         field: {
           fieldId: nanoid(8),
@@ -18,22 +18,25 @@ export const useDatabase = () => {
           title: 'new field',
         },
       })
-    );
+    );*/
+    console.log('depreciated');
   };
 
   const renameField = (fieldId: string, newTitle: string) => {
-    const field = database.fields[fieldId];
+    /*   const field = database.fields[fieldId];
     field.title = newTitle;
 
     dispatch(
       databaseActions.updateField({
         field,
       })
-    );
+    );*/
+    console.log('depreciated');
   };
 
   const newRow = () => {
-    dispatch(databaseActions.addRow());
+    // dispatch(databaseActions.addRow());
+    console.log('depreciated');
   };
 
   return {

+ 108 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/loadField.ts

@@ -0,0 +1,108 @@
+import { TypeOptionController } from '../../../stores/effects/database/field/type_option/type_option_controller';
+import { Some } from 'ts-results';
+import { IDatabaseField, ISelectOption } from '../../../stores/reducers/database/slice';
+import {
+  ChecklistTypeOptionPB,
+  DateFormat,
+  FieldType,
+  MultiSelectTypeOptionPB,
+  NumberFormat,
+  SingleSelectTypeOptionPB,
+  TimeFormat,
+} from '../../../../services/backend';
+import {
+  makeChecklistTypeOptionContext,
+  makeDateTypeOptionContext,
+  makeMultiSelectTypeOptionContext,
+  makeNumberTypeOptionContext,
+  makeSingleSelectTypeOptionContext,
+} from '../../../stores/effects/database/field/type_option/type_option_context';
+import { boardActions } from '../../../stores/reducers/board/slice';
+import { FieldInfo } from '../../../stores/effects/database/field/field_controller';
+import { AppDispatch } from '../../../stores/store';
+
+export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?: AppDispatch): Promise<IDatabaseField> {
+  const field = fieldInfo.field;
+  const typeOptionController = new TypeOptionController(viewId, Some(fieldInfo));
+
+  // temporary hack to set grouping field
+  let groupingFieldSelected = false;
+
+  switch (field.field_type) {
+    case FieldType.SingleSelect:
+    case FieldType.MultiSelect:
+    case FieldType.Checklist: {
+      let selectOptions: ISelectOption[] = [];
+      let typeOption: SingleSelectTypeOptionPB | MultiSelectTypeOptionPB | ChecklistTypeOptionPB | undefined;
+
+      if (field.field_type === FieldType.SingleSelect) {
+        typeOption = (await makeSingleSelectTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
+        if (!groupingFieldSelected) {
+          if (dispatch) {
+            dispatch(boardActions.setGroupingFieldId({ fieldId: field.id }));
+          }
+          groupingFieldSelected = true;
+        }
+      }
+      if (field.field_type === FieldType.MultiSelect) {
+        typeOption = (await makeMultiSelectTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
+      }
+      if (field.field_type === FieldType.Checklist) {
+        typeOption = (await makeChecklistTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
+      }
+
+      if (typeOption) {
+        selectOptions = typeOption.options.map<ISelectOption>((option) => {
+          return {
+            selectOptionId: option.id,
+            title: option.name,
+            color: option.color,
+          };
+        });
+      }
+
+      return {
+        fieldId: field.id,
+        title: field.name,
+        fieldType: field.field_type,
+        fieldOptions: {
+          selectOptions,
+        },
+      };
+    }
+
+    case FieldType.Number: {
+      const typeOption = (await makeNumberTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
+      return {
+        fieldId: field.id,
+        title: field.name,
+        fieldType: field.field_type,
+        fieldOptions: {
+          numberFormat: typeOption.format,
+        },
+      };
+    }
+
+    case FieldType.DateTime: {
+      const typeOption = (await makeDateTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
+      return {
+        fieldId: field.id,
+        title: field.name,
+        fieldType: field.field_type,
+        fieldOptions: {
+          dateFormat: typeOption.date_format,
+          timeFormat: typeOption.time_format,
+          includeTime: typeOption.include_time,
+        },
+      };
+    }
+
+    default: {
+      return {
+        fieldId: field.id,
+        title: field.name,
+        fieldType: field.field_type,
+      };
+    }
+  }
+}

+ 32 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useCell.ts

@@ -0,0 +1,32 @@
+import { CellIdentifier } from '../../../stores/effects/database/cell/cell_bd_svc';
+import { CellCache } from '../../../stores/effects/database/cell/cell_cache';
+import { FieldController } from '../../../stores/effects/database/field/field_controller';
+import { CellControllerBuilder } from '../../../stores/effects/database/cell/controller_builder';
+import { DateCellDataPB, SelectOptionCellDataPB, URLCellDataPB } from '../../../../services/backend';
+import { useEffect, useState } from 'react';
+
+export const useCell = (cellIdentifier: CellIdentifier, cellCache: CellCache, fieldController: FieldController) => {
+  const [data, setData] = useState<DateCellDataPB | URLCellDataPB | SelectOptionCellDataPB | string | undefined>();
+
+  useEffect(() => {
+    const builder = new CellControllerBuilder(cellIdentifier, cellCache, fieldController);
+    const cellController = builder.build();
+    cellController.subscribeChanged({
+      onCellChanged: (value) => {
+        setData(value.unwrap());
+      },
+    });
+
+    // ignore the return value, because we are using the subscription
+    void cellController.getCellData();
+
+    // dispose the cell controller when the component is unmounted
+    return () => {
+      void cellController.dispose();
+    };
+  }, []);
+
+  return {
+    data,
+  };
+};

+ 68 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useDatabase.ts

@@ -0,0 +1,68 @@
+import { useEffect, useState } from 'react';
+import { DatabaseController } from '../../../stores/effects/database/database_controller';
+import {
+  databaseActions,
+  DatabaseFieldMap,
+  IDatabaseColumn,
+  IDatabaseRow,
+} from '../../../stores/reducers/database/slice';
+import { useAppDispatch, useAppSelector } from '../../../stores/store';
+import loadField from './loadField';
+import { FieldInfo } from '../../../stores/effects/database/field/field_controller';
+import { RowInfo } from '../../../stores/effects/database/row/row_cache';
+
+export const useDatabase = (viewId: string) => {
+  const dispatch = useAppDispatch();
+  const databaseStore = useAppSelector((state) => state.database);
+  const boardStore = useAppSelector((state) => state.board);
+  const [controller, setController] = useState<DatabaseController>();
+  const [rows, setRows] = useState<readonly RowInfo[]>([]);
+
+  useEffect(() => {
+    if (!viewId.length) return;
+    const c = new DatabaseController(viewId);
+    setController(c);
+
+    // on unmount dispose the controller
+    return () => void c.dispose();
+  }, [viewId]);
+
+  const loadFields = async (fieldInfos: readonly FieldInfo[]) => {
+    const fields: DatabaseFieldMap = {};
+    const columns: IDatabaseColumn[] = [];
+
+    for (const fieldInfo of fieldInfos) {
+      const fieldPB = fieldInfo.field;
+      columns.push({
+        fieldId: fieldPB.id,
+        sort: 'none',
+        visible: fieldPB.visibility,
+      });
+
+      const field = await loadField(viewId, fieldInfo, dispatch);
+      fields[field.fieldId] = field;
+    }
+
+    dispatch(databaseActions.updateFields({ fields }));
+    dispatch(databaseActions.updateColumns({ columns }));
+    console.log(fields, columns);
+  };
+
+  useEffect(() => {
+    if (!controller) return;
+
+    void (async () => {
+      controller.subscribe({
+        onRowsChanged: (rowInfos) => {
+          setRows(rowInfos);
+        },
+        onFieldsChanged: (fieldInfos) => {
+          void loadFields(fieldInfos);
+        },
+      });
+      await controller.open();
+    })();
+  }, [controller]);
+
+  return { loadFields, controller, rows };
+};

+ 43 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useRow.ts

@@ -0,0 +1,43 @@
+import { DatabaseController } from '../../../stores/effects/database/database_controller';
+import { RowController } from '../../../stores/effects/database/row/row_controller';
+import { RowInfo } from '../../../stores/effects/database/row/row_cache';
+import { CellIdentifier } from '../../../stores/effects/database/cell/cell_bd_svc';
+import { useEffect, useState } from 'react';
+
+export const useRow = (viewId: string, databaseController: DatabaseController, rowInfo: RowInfo) => {
+  const [cells, setCells] = useState<{ fieldId: string; cellIdentifier: CellIdentifier }[]>([]);
+  const [rowController, setRowController] = useState<RowController>();
+
+  useEffect(() => {
+    const rowCache = databaseController.databaseViewCache.getRowCache();
+    const fieldController = databaseController.fieldController;
+    const c = new RowController(rowInfo, fieldController, rowCache);
+    setRowController(c);
+
+    return () => {
+      // dispose row controller in future
+    };
+  }, []);
+
+  useEffect(() => {
+    if (!rowController) return;
+
+    void (async () => {
+      const cellsPB = await rowController.loadCells();
+      const loadingCells: { fieldId: string; cellIdentifier: CellIdentifier }[] = [];
+
+      for (const [fieldId, cellIdentifier] of cellsPB.entries()) {
+        loadingCells.push({
+          fieldId,
+          cellIdentifier,
+        });
+      }
+
+      setCells(loadingCells);
+    })();
+  }, [rowController]);
+
+  return {
+    cells: cells,
+  };
+};

+ 1 - 0
frontend/appflowy_tauri/src/appflowy_app/components/auth/ProtectedRoutes.tsx

@@ -5,6 +5,7 @@ import { useEffect, useState } from 'react';
 import { GetStarted } from './GetStarted/GetStarted';
 import { AppflowyLogo } from '../_shared/svg/AppflowyLogo';
 
+
 export const ProtectedRoutes = () => {
   const { currentUser, checkUser } = useAuth();
   const [isLoading, setIsLoading] = useState(true);

+ 7 - 17
frontend/appflowy_tauri/src/appflowy_app/components/board/Board.hooks.ts

@@ -1,34 +1,24 @@
 import { useEffect, useState } from 'react';
 import { useAppDispatch, useAppSelector } from '../../stores/store';
 import { boardActions } from '../../stores/reducers/board/slice';
-import { ICellData, IDatabase, IDatabaseRow, ISelectOption } from '../../stores/reducers/database/slice';
+import { ISelectOption, ISelectOptionType } from '../../stores/reducers/database/slice';
 
 export const useBoard = () => {
   const dispatch = useAppDispatch();
   const groupingFieldId = useAppSelector((state) => state.board);
   const database = useAppSelector((state) => state.database);
   const [title, setTitle] = useState('');
-  const [boardColumns, setBoardColumns] =
-    useState<(ISelectOption & { rows: (IDatabaseRow & { isGhost: boolean })[] })[]>();
+  const [boardColumns, setBoardColumns] = useState<ISelectOption[]>([]);
   const [movingRowId, setMovingRowId] = useState<string | undefined>(undefined);
   const [ghostLocation, setGhostLocation] = useState<{ column: number; row: number }>({ column: 0, row: 0 });
 
   useEffect(() => {
     setTitle(database.title);
-    setBoardColumns(
-      database.fields[groupingFieldId].fieldOptions.selectOptions?.map((groupFieldItem) => {
-        const rows = database.rows
-          .filter((row) => row.cells[groupingFieldId].optionIds?.some((so) => so === groupFieldItem.selectOptionId))
-          .map((row) => ({
-            ...row,
-            isGhost: false,
-          }));
-        return {
-          ...groupFieldItem,
-          rows: rows,
-        };
-      }) || []
-    );
+    if (database.fields[groupingFieldId]) {
+      setBoardColumns(
+        (database.fields[groupingFieldId].fieldOptions as ISelectOptionType | undefined)?.selectOptions || []
+      );
+    }
   }, [database, groupingFieldId]);
 
   const changeGroupingField = (fieldId: string) => {

+ 8 - 9
frontend/appflowy_tauri/src/appflowy_app/components/board/Board.tsx

@@ -1,13 +1,13 @@
 import { SettingsSvg } from '../_shared/svg/SettingsSvg';
 import { SearchInput } from '../_shared/SearchInput';
-import { useDatabase } from '../_shared/Database.hooks';
 import { BoardBlock } from './BoardBlock';
 import { NewBoardBlock } from './NewBoardBlock';
-import { IDatabaseRow } from '../../stores/reducers/database/slice';
 import { useBoard } from './Board.hooks';
+import { useDatabase } from '../_shared/database-hooks/useDatabase';
+
+export const Board = ({ viewId }: { viewId: string }) => {
+  const { controller, rows } = useDatabase(viewId);
 
-export const Board = () => {
-  const { database, newField, renameField, newRow } = useDatabase();
   const {
     title,
     boardColumns,
@@ -36,16 +36,15 @@ export const Board = () => {
       </div>
       <div className={'relative w-full flex-1 overflow-auto'}>
         <div className={'absolute flex h-full flex-shrink-0 items-start justify-start gap-4'}>
-          {database &&
+          {controller &&
             boardColumns?.map((column, index) => (
               <BoardBlock
+                viewId={viewId}
+                controller={controller}
                 key={index}
                 title={column.title}
+                rows={rows}
                 groupingFieldId={groupingFieldId}
-                count={column.rows.length}
-                fields={database.fields}
-                columns={database.columns}
-                rows={column.rows}
                 startMove={startMove}
                 endMove={endMove}
               />

+ 14 - 15
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardBlock.tsx

@@ -1,24 +1,23 @@
 import { Details2Svg } from '../_shared/svg/Details2Svg';
 import AddSvg from '../_shared/svg/AddSvg';
-import { DatabaseFieldMap, ICellData, IDatabaseColumn, IDatabaseRow } from '../../stores/reducers/database/slice';
-import { BoardBlockItem } from './BoardBlockItem';
+import { BoardCard } from './BoardCard';
+import { RowInfo } from '../../stores/effects/database/row/row_cache';
+import { DatabaseController } from '../../stores/effects/database/database_controller';
 
 export const BoardBlock = ({
+  viewId,
+  controller,
   title,
   groupingFieldId,
-  count,
-  fields,
-  columns,
   rows,
   startMove,
   endMove,
 }: {
+  viewId: string;
+  controller: DatabaseController;
   title: string;
   groupingFieldId: string;
-  count: number;
-  fields: DatabaseFieldMap;
-  columns: IDatabaseColumn[];
-  rows: IDatabaseRow[];
+  rows: readonly RowInfo[];
   startMove: (id: string) => void;
   endMove: () => void;
 }) => {
@@ -27,7 +26,7 @@ export const BoardBlock = ({
       <div className={'flex items-center justify-between p-4'}>
         <div className={'flex items-center gap-2'}>
           <span>{title}</span>
-          <span className={'text-shade-4'}>({count})</span>
+          <span className={'text-shade-4'}>()</span>
         </div>
         <div className={'flex items-center gap-2'}>
           <button className={'h-5 w-5 rounded hover:bg-surface-2'}>
@@ -40,15 +39,15 @@ export const BoardBlock = ({
       </div>
       <div className={'flex flex-1 flex-col gap-1 overflow-auto px-2'}>
         {rows.map((row, index) => (
-          <BoardBlockItem
+          <BoardCard
+            viewId={viewId}
+            controller={controller}
             key={index}
             groupingFieldId={groupingFieldId}
-            fields={fields}
-            columns={columns}
             row={row}
-            startMove={() => startMove(row.rowId)}
+            startMove={() => startMove(row.row.id)}
             endMove={() => endMove()}
-          ></BoardBlockItem>
+          ></BoardCard>
         ))}
       </div>
       <div className={'p-2'}>

+ 27 - 31
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardBlockItem.tsx → frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCard.tsx

@@ -3,22 +3,34 @@ import { Details2Svg } from '../_shared/svg/Details2Svg';
 import { FieldType } from '../../../services/backend';
 import { getBgColor } from '../_shared/getColor';
 import { MouseEventHandler, useEffect, useRef, useState } from 'react';
+import { RowInfo } from '../../stores/effects/database/row/row_cache';
+import { useRow } from '../_shared/database-hooks/useRow';
+import { DatabaseController } from '../../stores/effects/database/database_controller';
+import { useAppSelector } from '../../stores/store';
+import { BoardCell } from './BoardCell';
 
-export const BoardBlockItem = ({
+export const BoardCard = ({
+  viewId,
+  controller,
   groupingFieldId,
-  fields,
-  columns,
+  // fields,
+  // columns,
   row,
   startMove,
   endMove,
 }: {
+  viewId: string;
+  controller: DatabaseController;
   groupingFieldId: string;
-  fields: DatabaseFieldMap;
-  columns: IDatabaseColumn[];
-  row: IDatabaseRow;
+  // fields: DatabaseFieldMap;
+  // columns: IDatabaseColumn[];
+  row: RowInfo;
   startMove: () => void;
   endMove: () => void;
 }) => {
+  const { cells } = useRow(viewId, controller, row);
+
+  const databaseStore = useAppSelector((state) => state.database);
   const [isMoving, setIsMoving] = useState(false);
   const [isDown, setIsDown] = useState(false);
   const [ghostWidth, setGhostWidth] = useState(0);
@@ -26,6 +38,7 @@ export const BoardBlockItem = ({
   const [ghostLeft, setGhostLeft] = useState(0);
   const [ghostTop, setGhostTop] = useState(0);
   const el = useRef<HTMLDivElement>(null);
+
   useEffect(() => {
     if (el.current?.getBoundingClientRect && isMoving) {
       const { left, top, width, height } = el.current.getBoundingClientRect();
@@ -74,31 +87,14 @@ export const BoardBlockItem = ({
           <Details2Svg></Details2Svg>
         </button>
         <div className={'flex flex-col gap-3'}>
-          {columns
-            .filter((column) => column.fieldId !== groupingFieldId)
-            .map((column, index) => {
-              switch (fields[column.fieldId].fieldType) {
-                case FieldType.MultiSelect:
-                  return (
-                    <div key={index} className={'flex flex-wrap items-center gap-2'}>
-                      {row.cells[column.fieldId].optionIds?.map((option, indexOption) => {
-                        const selectOptions = fields[column.fieldId].fieldOptions.selectOptions;
-                        const selectedOption = selectOptions?.find((so) => so.selectOptionId === option);
-                        return (
-                          <div
-                            key={indexOption}
-                            className={`rounded px-1 py-0.5 text-sm ${getBgColor(selectedOption?.color)}`}
-                          >
-                            {selectedOption?.title}
-                          </div>
-                        );
-                      })}
-                    </div>
-                  );
-                default:
-                  return <div key={index}>{row.cells[column.fieldId].data}</div>;
-              }
-            })}
+          {cells.map((cell, index) => (
+            <BoardCell
+              key={index}
+              cellIdentifier={cell.cellIdentifier}
+              cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
+              fieldController={controller.fieldController}
+            ></BoardCell>
+          ))}
         </div>
       </div>
       {isMoving && (

+ 43 - 0
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCell.tsx

@@ -0,0 +1,43 @@
+import { CellIdentifier } from '../../stores/effects/database/cell/cell_bd_svc';
+import { CellCache } from '../../stores/effects/database/cell/cell_cache';
+import { FieldController } from '../../stores/effects/database/field/field_controller';
+import { FieldType } from '../../../services/backend';
+import { BoardOptionsCell } from './BoardOptionsCell';
+import { BoardDateCell } from './BoardDateCell';
+import { BoardTextCell } from './BoardTextCell';
+
+export const BoardCell = ({
+  cellIdentifier,
+  cellCache,
+  fieldController,
+}: {
+  cellIdentifier: CellIdentifier;
+  cellCache: CellCache;
+  fieldController: FieldController;
+}) => {
+  return (
+    <>
+      {cellIdentifier.fieldType === FieldType.SingleSelect ||
+      cellIdentifier.fieldType === FieldType.MultiSelect ||
+      cellIdentifier.fieldType === FieldType.Checklist ? (
+        <BoardOptionsCell
+          cellIdentifier={cellIdentifier}
+          cellCache={cellCache}
+          fieldController={fieldController}
+        ></BoardOptionsCell>
+      ) : cellIdentifier.fieldType === FieldType.DateTime ? (
+        <BoardDateCell
+          cellIdentifier={cellIdentifier}
+          cellCache={cellCache}
+          fieldController={fieldController}
+        ></BoardDateCell>
+      ) : (
+        <BoardTextCell
+          cellIdentifier={cellIdentifier}
+          cellCache={cellCache}
+          fieldController={fieldController}
+        ></BoardTextCell>
+      )}
+    </>
+  );
+};

+ 18 - 0
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardDateCell.tsx

@@ -0,0 +1,18 @@
+import { DateCellDataPB } from '../../../services/backend';
+import { useCell } from '../_shared/database-hooks/useCell';
+import { CellIdentifier } from '../../stores/effects/database/cell/cell_bd_svc';
+import { CellCache } from '../../stores/effects/database/cell/cell_cache';
+import { FieldController } from '../../stores/effects/database/field/field_controller';
+
+export const BoardDateCell = ({
+  cellIdentifier,
+  cellCache,
+  fieldController,
+}: {
+  cellIdentifier: CellIdentifier;
+  cellCache: CellCache;
+  fieldController: FieldController;
+}) => {
+  const { data } = useCell(cellIdentifier, cellCache, fieldController);
+  return <div>{(data as DateCellDataPB | undefined)?.date || ''}</div>;
+};

+ 25 - 0
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardOptionsCell.tsx

@@ -0,0 +1,25 @@
+import { SelectOptionCellDataPB } from '../../../services/backend';
+import { useCell } from '../_shared/database-hooks/useCell';
+import { CellIdentifier } from '../../stores/effects/database/cell/cell_bd_svc';
+import { CellCache } from '../../stores/effects/database/cell/cell_cache';
+import { FieldController } from '../../stores/effects/database/field/field_controller';
+
+export const BoardOptionsCell = ({
+  cellIdentifier,
+  cellCache,
+  fieldController,
+}: {
+  cellIdentifier: CellIdentifier;
+  cellCache: CellCache;
+  fieldController: FieldController;
+}) => {
+  const { data } = useCell(cellIdentifier, cellCache, fieldController);
+
+  return (
+    <>
+      {(data as SelectOptionCellDataPB | undefined)?.select_options?.map((option, index) => (
+        <div key={index}>{option?.name || ''}</div>
+      )) || ''}
+    </>
+  );
+};

+ 18 - 0
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardTextCell.tsx

@@ -0,0 +1,18 @@
+import { CellIdentifier } from '../../stores/effects/database/cell/cell_bd_svc';
+import { CellCache } from '../../stores/effects/database/cell/cell_cache';
+import { FieldController } from '../../stores/effects/database/field/field_controller';
+import { useCell } from '../_shared/database-hooks/useCell';
+
+export const BoardTextCell = ({
+  cellIdentifier,
+  cellCache,
+  fieldController,
+}: {
+  cellIdentifier: CellIdentifier;
+  cellCache: CellCache;
+  fieldController: FieldController;
+}) => {
+  const { data } = useCell(cellIdentifier, cellCache, fieldController);
+
+  return <div>{(data as string | undefined) || ''}</div>;
+};

+ 9 - 1
frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/Breadcrumbs.tsx

@@ -1,7 +1,15 @@
-export const Breadcrumbs = () => {
+import { ShowMenuSvg } from '../../_shared/svg/ShowMenuSvg';
+
+export const Breadcrumbs = ({ menuHidden, onShowMenuClick }: { menuHidden: boolean; onShowMenuClick: () => void }) => {
   return (
     <div className={'flex items-center'}>
       <div className={'mr-4 flex items-center'}>
+        {menuHidden && (
+          <button onClick={() => onShowMenuClick()} className={'mr-2 h-5 w-5'}>
+            <ShowMenuSvg></ShowMenuSvg>
+          </button>
+        )}
+
         <button className={'p-1'} onClick={() => history.back()}>
           <img src={'/images/home/arrow_left.svg'} />
         </button>

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/HeaderPanel.tsx

@@ -1,10 +1,10 @@
 import { Breadcrumbs } from './Breadcrumbs';
 import { PageOptions } from './PageOptions';
 
-export const HeaderPanel = () => {
+export const HeaderPanel = ({ menuHidden, onShowMenuClick }: { menuHidden: boolean; onShowMenuClick: () => void }) => {
   return (
     <div className={'flex h-[60px] items-center justify-between border-b border-shade-6 px-8'}>
-      <Breadcrumbs></Breadcrumbs>
+      <Breadcrumbs menuHidden={menuHidden} onShowMenuClick={onShowMenuClick}></Breadcrumbs>
       <PageOptions></PageOptions>
     </div>
   );

+ 33 - 4
frontend/appflowy_tauri/src/appflowy_app/components/layout/MainPanel.tsx

@@ -1,11 +1,40 @@
-import { ReactNode } from 'react';
+import { ReactNode, useEffect, useState } from 'react';
 import { HeaderPanel } from './HeaderPanel/HeaderPanel';
 import { FooterPanel } from './FooterPanel';
 
-export const MainPanel = ({ children }: { children: ReactNode }) => {
+const ANIMATION_DURATION = 300;
+
+export const MainPanel = ({
+  left,
+  menuHidden,
+  onShowMenuClick,
+  children,
+}: {
+  left: number;
+  menuHidden: boolean;
+  onShowMenuClick: () => void;
+  children: ReactNode;
+}) => {
+  const [animation, setAnimation] = useState(false);
+  useEffect(() => {
+    if (!menuHidden) {
+      setTimeout(() => {
+        setAnimation(false);
+      }, ANIMATION_DURATION);
+    } else {
+      setAnimation(true);
+    }
+  }, [menuHidden]);
+
   return (
-    <div className={'flex h-full flex-1 flex-col'}>
-      <HeaderPanel></HeaderPanel>
+    <div
+      className={`absolute inset-0 flex h-full flex-1 flex-col`}
+      style={{
+        transition: menuHidden || animation ? `left ${ANIMATION_DURATION}ms ease-out` : 'none',
+        left: `${menuHidden ? 0 : left}px`,
+      }}
+    >
+      <HeaderPanel menuHidden={menuHidden} onShowMenuClick={onShowMenuClick}></HeaderPanel>
       <div className={'min-h-0 flex-1 overflow-auto'}>{children}</div>
       <FooterPanel></FooterPanel>
     </div>

+ 17 - 1
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.hooks.ts

@@ -1,19 +1,22 @@
-import { useAppSelector } from '../../../stores/store';
+import { useAppDispatch, useAppSelector } from '../../../stores/store';
 import { useNavigate } from 'react-router-dom';
 import { IPage } from '../../../stores/reducers/pages/slice';
 import { ViewLayoutTypePB } from '../../../../services/backend';
 import { MouseEventHandler, useState } from 'react';
+import { activePageIdActions } from '../../../stores/reducers/activePageId/slice';
 
 // number of pixels from left side of screen to show hidden navigation panel
 const FLOATING_PANEL_SHOW_WIDTH = 10;
 const FLOATING_PANEL_HIDE_EXTRA_WIDTH = 10;
 
 export const useNavigationPanelHooks = function () {
+  const dispatch = useAppDispatch();
   const folders = useAppSelector((state) => state.folders);
   const pages = useAppSelector((state) => state.pages);
   const width = useAppSelector((state) => state.navigationWidth);
   const [navigationPanelFixed, setNavigationPanelFixed] = useState(true);
   const [slideInFloatingPanel, setSlideInFloatingPanel] = useState(true);
+  const [menuHidden, setMenuHidden] = useState(false);
 
   const navigate = useNavigate();
 
@@ -28,6 +31,14 @@ export const useNavigationPanelHooks = function () {
 
   const [floatingPanelWidth, setFloatingPanelWidth] = useState(0);
 
+  const onHideMenuClick = () => {
+    setMenuHidden(true);
+  };
+
+  const onShowMenuClick = () => {
+    setMenuHidden(false);
+  };
+
   const onPageClick = (page: IPage) => {
     let pageTypeRoute = (() => {
       switch (page.pageType) {
@@ -43,6 +54,8 @@ export const useNavigationPanelHooks = function () {
       }
     })();
 
+    dispatch(activePageIdActions.setActivePageId(page.id));
+
     navigate(`/page/${pageTypeRoute}/${page.id}`);
   };
 
@@ -66,5 +79,8 @@ export const useNavigationPanelHooks = function () {
     onScreenMouseMove,
     slideInFloatingPanel,
     setFloatingPanelWidth,
+    menuHidden,
+    onHideMenuClick,
+    onShowMenuClick,
   };
 };

+ 18 - 6
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx

@@ -9,14 +9,19 @@ import { IPage } from '../../../stores/reducers/pages/slice';
 import { useNavigate } from 'react-router-dom';
 import React from 'react';
 
+const MINIMUM_WIDTH = 200;
+const ANIMATION_DURATION = 300;
+
 export const NavigationPanel = ({
-  onCollapseNavigationClick,
+  onHideMenuClick,
+  menuHidden,
   width,
   folders,
   pages,
   onPageClick,
 }: {
-  onCollapseNavigationClick: () => void;
+  onHideMenuClick: () => void;
+  menuHidden: boolean;
   width: number;
   folders: IFolder[];
   pages: IPage[];
@@ -24,9 +29,16 @@ export const NavigationPanel = ({
 }) => {
   return (
     <>
-      <div className={'flex flex-col justify-between bg-surface-1 text-sm'} style={{ width: `${width}px` }}>
+      <div
+        className={`absolute inset-0 flex flex-col justify-between bg-surface-1 text-sm`}
+        style={{
+          transition: `left ${ANIMATION_DURATION}ms ease-out`,
+          width: `${width}px`,
+          left: `${menuHidden ? -width : 0}px`,
+        }}
+      >
         <div className={'flex flex-col'}>
-          <AppLogo iconToShow={'hide'} onHideMenuClick={onCollapseNavigationClick}></AppLogo>
+          <AppLogo iconToShow={'hide'} onHideMenuClick={onHideMenuClick}></AppLogo>
           <WorkspaceUser></WorkspaceUser>
           <WorkspaceApps folders={folders} pages={pages} onPageClick={onPageClick} />
         </div>
@@ -46,7 +58,7 @@ export const NavigationPanel = ({
           <NewFolderButton></NewFolderButton>
         </div>
       </div>
-      <NavigationResizer></NavigationResizer>
+      <NavigationResizer minWidth={MINIMUM_WIDTH}></NavigationResizer>
     </>
   );
 };
@@ -58,7 +70,7 @@ type AppsContext = {
 };
 
 const WorkspaceApps: React.FC<AppsContext> = ({ folders, pages, onPageClick }) => (
-  <div className={'flex flex-col px-2'}>
+  <div className={'flex flex-col overflow-auto px-2'} style={{ height: 'calc(100vh - 300px)' }}>
     {folders.map((folder, index) => (
       <FolderItem
         key={index}

+ 6 - 2
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationResizer.tsx

@@ -3,13 +3,17 @@ import { useAppDispatch, useAppSelector } from '../../../stores/store';
 import { useEffect } from 'react';
 import { navigationWidthActions } from '../../../stores/reducers/navigation-width/slice';
 
-export const NavigationResizer = () => {
+export const NavigationResizer = ({ minWidth }: { minWidth: number }) => {
   const width = useAppSelector((state) => state.navigationWidth);
   const appDispatch = useAppDispatch();
   const { onMouseDown, movementX } = useResizer();
 
   useEffect(() => {
-    appDispatch(navigationWidthActions.changeWidth(width + movementX));
+    if (width + movementX < minWidth) {
+      appDispatch(navigationWidthActions.changeWidth(minWidth));
+    } else {
+      appDispatch(navigationWidthActions.changeWidth(width + movementX));
+    }
   }, [movementX]);
 
   return (

+ 3 - 1
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.hooks.ts

@@ -1,5 +1,5 @@
 import { IPage, pagesActions } from '../../../stores/reducers/pages/slice';
-import { useAppDispatch } from '../../../stores/store';
+import { useAppDispatch, useAppSelector } from '../../../stores/store';
 import { useState } from 'react';
 import { nanoid } from 'nanoid';
 import { ViewBackendService } from '../../../stores/effects/folder/view/view_bd_svc';
@@ -9,6 +9,7 @@ export const usePageEvents = (page: IPage) => {
   const appDispatch = useAppDispatch();
   const [showPageOptions, setShowPageOptions] = useState(false);
   const [showRenamePopup, setShowRenamePopup] = useState(false);
+  const activePageId = useAppSelector((state) => state.activePageId);
   const viewBackendService: ViewBackendService = new ViewBackendService(page.id);
   const error = useError();
 
@@ -69,5 +70,6 @@ export const usePageEvents = (page: IPage) => {
     duplicatePage,
     closePopup,
     closeRenamePopup,
+    activePageId,
   };
 };

+ 4 - 1
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.tsx

@@ -20,13 +20,16 @@ export const PageItem = ({ page, onPageClick }: { page: IPage; onPageClick: () =
     duplicatePage,
     closePopup,
     closeRenamePopup,
+    activePageId,
   } = usePageEvents(page);
 
   return (
     <div className={'relative'}>
       <div
         onClick={() => onPageClick()}
-        className={'flex cursor-pointer items-center justify-between rounded-lg py-2 pl-8 pr-4 hover:bg-surface-2 '}
+        className={`flex cursor-pointer items-center justify-between rounded-lg py-2 pl-8 pr-4 hover:bg-surface-2 ${
+          activePageId === page.id ? 'bg-surface-2' : ''
+        }`}
       >
         <button className={'flex min-w-0 flex-1 items-center'}>
           <i className={'ml-1 mr-1 h-[16px] w-[16px]'}>

+ 14 - 19
frontend/appflowy_tauri/src/appflowy_app/components/layout/Screen.tsx

@@ -26,30 +26,25 @@ export const Screen = ({ children }: { children: ReactNode }) => {
     onScreenMouseMove,
     slideInFloatingPanel,
     setFloatingPanelWidth,
+    onHideMenuClick,
+    onShowMenuClick,
+    menuHidden,
   } = useNavigationPanelHooks();
 
   return (
     <div onMouseMove={onScreenMouseMove} className='flex h-screen w-screen bg-white text-black'>
-      {navigationPanelFixed ? (
-        <NavigationPanel
-          onCollapseNavigationClick={onCollapseNavigationClick}
-          width={width}
-          folders={folders}
-          pages={pages}
-          onPageClick={onPageClick}
-        ></NavigationPanel>
-      ) : (
-        <NavigationFloatingPanel
-          onFixNavigationClick={onFixNavigationClick}
-          slideInFloatingPanel={slideInFloatingPanel}
-          folders={folders}
-          pages={pages}
-          onPageClick={onPageClick}
-          setWidth={setFloatingPanelWidth}
-        ></NavigationFloatingPanel>
-      )}
+      <NavigationPanel
+        onHideMenuClick={onHideMenuClick}
+        width={width}
+        folders={folders}
+        pages={pages}
+        onPageClick={onPageClick}
+        menuHidden={menuHidden}
+      ></NavigationPanel>
 
-      <MainPanel>{children}</MainPanel>
+      <MainPanel left={width} menuHidden={menuHidden} onShowMenuClick={onShowMenuClick}>
+        {children}
+      </MainPanel>
     </div>
   );
 };

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/tests/DatabaseTestHelper.ts

@@ -47,7 +47,7 @@ export async function assertTextCell(
   const cellController = await makeTextCellController(fieldId, rowInfo, databaseController).then((result) =>
     result.unwrap()
   );
-  await cellController.subscribeChanged({
+  cellController.subscribeChanged({
     onCellChanged: (value) => {
       const cellContent = value.unwrap();
       if (cellContent !== expectedContent) {

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/cell_controller.ts

@@ -55,7 +55,7 @@ export class CellController<T, D> {
     });
   }
 
-  subscribeChanged = async (callbacks: Callbacks<T>) => {
+  subscribeChanged = (callbacks: Callbacks<T>) => {
     this.subscribeCallbacks = callbacks;
     this.cellDataNotifier.observer.subscribe((cellData) => {
       if (cellData !== null) {

+ 1 - 2
frontend/appflowy_tauri/src/appflowy_app/stores/effects/folder/notifications/observer.ts

@@ -1,5 +1,4 @@
-import { OnNotificationError } from '../../../../../services/backend/notifications';
-import { AFNotificationObserver } from '../../../../../services/backend/notifications';
+import { OnNotificationError, AFNotificationObserver } from '../../../../../services/backend/notifications';
 import { FolderNotificationParser } from './parser';
 import { FlowyError, FolderNotification } from '../../../../../services/backend';
 import { Result } from 'ts-results';

+ 12 - 0
frontend/appflowy_tauri/src/appflowy_app/stores/reducers/activePageId/slice.ts

@@ -0,0 +1,12 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+export const activePageIdSlice = createSlice({
+  name: 'activePageId',
+  initialState: '',
+  reducers: {
+    setActivePageId(state, action: PayloadAction<string>) {
+      return action.payload;
+    },
+  },
+});
+
+export const activePageIdActions = activePageIdSlice.actions;

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/stores/reducers/board/slice.ts

@@ -1,6 +1,6 @@
 import { createSlice, PayloadAction } from '@reduxjs/toolkit';
 
-const initialState = 'field1';
+const initialState = '';
 
 export const boardSlice = createSlice({
   name: 'board',

+ 55 - 206
frontend/appflowy_tauri/src/appflowy_app/stores/reducers/database/slice.ts

@@ -1,5 +1,4 @@
 import { createSlice, PayloadAction } from '@reduxjs/toolkit';
-import { nanoid } from 'nanoid';
 import { FieldType } from '../../../../services/backend/models/flowy-database/field_entities';
 import { DateFormat, NumberFormat, SelectOptionColorPB, TimeFormat } from '../../../../services/backend';
 
@@ -9,42 +8,35 @@ export interface ISelectOption {
   color?: SelectOptionColorPB;
 }
 
-export interface IFieldOptions {
-  selectOptions?: ISelectOption[];
-  dateFormat?: DateFormat;
-  timeFormat?: TimeFormat;
-  includeTime?: boolean;
-  numberFormat?: NumberFormat;
+export interface ISelectOptionType {
+  selectOptions: ISelectOption[];
+}
+
+export interface IDateType {
+  dateFormat: DateFormat;
+  timeFormat: TimeFormat;
+  includeTime: boolean;
+}
+
+export interface INumberType {
+  numberFormat: NumberFormat;
 }
 
 export interface IDatabaseField {
   fieldId: string;
   title: string;
   fieldType: FieldType;
-  fieldOptions: IFieldOptions;
+  fieldOptions?: ISelectOptionType | IDateType | INumberType;
 }
 
 export interface IDatabaseColumn {
   fieldId: string;
   sort: 'none' | 'asc' | 'desc';
-  filter?: any;
   visible: boolean;
 }
 
-export interface ICellData {
-  rowId: string;
-  fieldId: string;
-  cellId: string;
-  data: string | number;
-  optionIds?: string[];
-}
-
-export type DatabaseCellMap = { [keys: string]: ICellData };
-
 export interface IDatabaseRow {
   rowId: string;
-  // key(fieldId) -> value(Cell)
-  cells: DatabaseCellMap;
 }
 
 export type DatabaseFieldMap = { [keys: string]: IDatabaseField };
@@ -56,190 +48,47 @@ export interface IDatabase {
   columns: IDatabaseColumn[];
 }
 
-// key(databaseId) -> value(IDatabase)
 const initialState: IDatabase = {
   title: 'Database One',
-  columns: [
-    {
-      visible: true,
-      fieldId: 'field1',
-      sort: 'none',
-    },
-    {
-      visible: true,
-      fieldId: 'field2',
-      sort: 'none',
-    },
-    {
-      visible: true,
-      fieldId: 'field3',
-      sort: 'none',
-    },
-    {
-      visible: true,
-      fieldId: 'field4',
-      sort: 'none',
-    },
-  ],
-  fields: {
-    field1: {
-      title: 'status',
-      fieldId: 'field1',
-      fieldType: FieldType.SingleSelect,
-      fieldOptions: {
-        selectOptions: [
-          {
-            selectOptionId: 'so1',
-            title: 'To Do',
-            color: SelectOptionColorPB.Orange,
-          },
-          {
-            selectOptionId: 'so2',
-            title: 'In Progress',
-            color: SelectOptionColorPB.Green,
-          },
-          {
-            selectOptionId: 'so3',
-            title: 'Done',
-            color: SelectOptionColorPB.Blue,
-          },
-        ],
-      },
-    },
-    field2: {
-      title: 'name',
-      fieldId: 'field2',
-      fieldType: FieldType.RichText,
-      fieldOptions: {},
-    },
-    field3: {
-      title: 'percent',
-      fieldId: 'field3',
-      fieldType: FieldType.Number,
-      fieldOptions: {
-        numberFormat: NumberFormat.Num,
-      },
-    },
-    field4: {
-      title: 'tags',
-      fieldId: 'field4',
-      fieldType: FieldType.MultiSelect,
-      fieldOptions: {
-        selectOptions: [
-          {
-            selectOptionId: 'f4so1',
-            title: 'type1',
-            color: SelectOptionColorPB.Blue,
-          },
-          {
-            selectOptionId: 'f4so2',
-            title: 'type2',
-            color: SelectOptionColorPB.Aqua,
-          },
-          {
-            selectOptionId: 'f4so3',
-            title: 'type3',
-            color: SelectOptionColorPB.Purple,
-          },
-          {
-            selectOptionId: 'f4so4',
-            title: 'type4',
-            color: SelectOptionColorPB.Purple,
-          },
-          {
-            selectOptionId: 'f4so5',
-            title: 'type5',
-            color: SelectOptionColorPB.Purple,
-          },
-          {
-            selectOptionId: 'f4so6',
-            title: 'type6',
-            color: SelectOptionColorPB.Purple,
-          },
-          {
-            selectOptionId: 'f4so7',
-            title: 'type7',
-            color: SelectOptionColorPB.Purple,
-          },
-        ],
-      },
-    },
-  },
-  rows: [
-    {
-      rowId: 'row1',
-      cells: {
-        field1: {
-          rowId: 'row1',
-          fieldId: 'field1',
-          cellId: 'cell11',
-          data: '',
-          optionIds: ['so1'],
-        },
-        field2: {
-          rowId: 'row1',
-          fieldId: 'field2',
-          cellId: 'cell12',
-          data: 'Card 1',
-        },
-        field3: {
-          rowId: 'row1',
-          fieldId: 'field3',
-          cellId: 'cell13',
-          data: 10,
-        },
-        field4: {
-          rowId: 'row1',
-          fieldId: 'field4',
-          cellId: 'cell14',
-          data: '',
-          optionIds: ['f4so2', 'f4so3', 'f4so4', 'f4so5', 'f4so6', 'f4so7'],
-        },
-      },
-    },
-    {
-      rowId: 'row2',
-      cells: {
-        field1: {
-          rowId: 'row2',
-          fieldId: 'field1',
-          cellId: 'cell21',
-          data: '',
-          optionIds: ['so1'],
-        },
-        field2: {
-          rowId: 'row2',
-          fieldId: 'field2',
-          cellId: 'cell22',
-          data: 'Card 2',
-        },
-        field3: {
-          rowId: 'row2',
-          fieldId: 'field3',
-          cellId: 'cell23',
-          data: 20,
-        },
-        field4: {
-          rowId: 'row2',
-          fieldId: 'field4',
-          cellId: 'cell24',
-          data: '',
-          optionIds: ['f4so1'],
-        },
-      },
-    },
-  ],
+  columns: [],
+  fields: {},
+  rows: [],
 };
 
 export const databaseSlice = createSlice({
   name: 'database',
   initialState: initialState,
   reducers: {
+    clear: () => {
+      return initialState;
+    },
+
+    updateRows: (state, action: PayloadAction<{ rows: IDatabaseRow[] }>) => {
+      return {
+        ...state,
+        rows: action.payload.rows,
+      };
+    },
+
+    updateFields: (state, action: PayloadAction<{ fields: DatabaseFieldMap }>) => {
+      return {
+        ...state,
+        fields: action.payload.fields,
+      };
+    },
+
+    updateColumns: (state, action: PayloadAction<{ columns: IDatabaseColumn[] }>) => {
+      return {
+        ...state,
+        columns: action.payload.columns,
+      };
+    },
+
     updateTitle: (state, action: PayloadAction<{ title: string }>) => {
       state.title = action.payload.title;
     },
 
-    addField: (state, action: PayloadAction<{ field: IDatabaseField }>) => {
+    /*addField: (state, action: PayloadAction<{ field: IDatabaseField }>) => {
       const { field } = action.payload;
 
       state.fields[field.fieldId] = field;
@@ -253,7 +102,7 @@ export const databaseSlice = createSlice({
         cells[field.fieldId] = {
           rowId: r.rowId,
           fieldId: field.fieldId,
-          data: '',
+          data: [''],
           cellId: nanoid(6),
         };
         return {
@@ -261,15 +110,15 @@ export const databaseSlice = createSlice({
           cells: cells,
         };
       });
-    },
+    },*/
 
-    updateField: (state, action: PayloadAction<{ field: IDatabaseField }>) => {
+    /*updateField: (state, action: PayloadAction<{ field: IDatabaseField }>) => {
       const { field } = action.payload;
 
       state.fields[field.fieldId] = field;
-    },
+    },*/
 
-    addFieldSelectOption: (state, action: PayloadAction<{ fieldId: string; option: ISelectOption }>) => {
+    /*addFieldSelectOption: (state, action: PayloadAction<{ fieldId: string; option: ISelectOption }>) => {
       const { fieldId, option } = action.payload;
 
       const field = state.fields[fieldId];
@@ -283,9 +132,9 @@ export const databaseSlice = createSlice({
           selectOptions: [option],
         };
       }
-    },
+    },*/
 
-    updateFieldSelectOption: (state, action: PayloadAction<{ fieldId: string; option: ISelectOption }>) => {
+    /*updateFieldSelectOption: (state, action: PayloadAction<{ fieldId: string; option: ISelectOption }>) => {
       const { fieldId, option } = action.payload;
 
       const field = state.fields[fieldId];
@@ -293,16 +142,16 @@ export const databaseSlice = createSlice({
       if (selectOptions) {
         selectOptions[selectOptions.findIndex((o) => o.selectOptionId === option.selectOptionId)] = option;
       }
-    },
+    },*/
 
-    addRow: (state) => {
+    /*addRow: (state) => {
       const rowId = nanoid(6);
       const cells: { [keys: string]: ICellData } = {};
       Object.keys(state.fields).forEach((id) => {
         cells[id] = {
           rowId: rowId,
           fieldId: id,
-          data: '',
+          data: [''],
           cellId: nanoid(6),
         };
       });
@@ -312,15 +161,15 @@ export const databaseSlice = createSlice({
       };
 
       state.rows.push(newRow);
-    },
+    },*/
 
-    updateCellValue: (source, action: PayloadAction<{ cell: ICellData }>) => {
+    /*updateCellValue: (source, action: PayloadAction<{ cell: ICellData }>) => {
       const { cell } = action.payload;
       const row = source.rows.find((r) => r.rowId === cell.rowId);
       if (row) {
         row.cells[cell.fieldId] = cell;
       }
-    },
+    },*/
   },
 });
 

+ 2 - 0
frontend/appflowy_tauri/src/appflowy_app/stores/store.ts

@@ -16,6 +16,7 @@ import { workspaceSlice } from './reducers/workspace/slice';
 import { databaseSlice } from './reducers/database/slice';
 import { boardSlice } from './reducers/board/slice';
 import { errorSlice } from './reducers/error/slice';
+import { activePageIdSlice } from './reducers/activePageId/slice';
 
 const listenerMiddlewareInstance = createListenerMiddleware({
   onError: () => console.error,
@@ -25,6 +26,7 @@ const store = configureStore({
   reducer: {
     [foldersSlice.name]: foldersSlice.reducer,
     [pagesSlice.name]: pagesSlice.reducer,
+    [activePageIdSlice.name]: activePageIdSlice.reducer,
     [navigationWidthSlice.name]: navigationWidthSlice.reducer,
     [currentUserSlice.name]: currentUserSlice.reducer,
     [gridSlice.name]: gridSlice.reducer,

+ 10 - 11
frontend/appflowy_tauri/src/appflowy_app/utils/log.ts

@@ -1,21 +1,20 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
 export class Log {
-  static error(msg?: any) {
-    console.log(msg);
+  static error(...msg: unknown[]) {
+    console.log(...msg);
   }
-  static info(msg?: any) {
-    console.log(msg);
+  static info(...msg: unknown[]) {
+    console.log(...msg);
   }
 
-  static debug(msg?: any) {
-    console.log(msg);
+  static debug(...msg: unknown[]) {
+    console.log(...msg);
   }
 
-  static trace(msg?: any) {
-    console.log(msg);
+  static trace(...msg: unknown[]) {
+    console.log(...msg);
   }
 
-  static warn(msg?: any) {
-    console.log(msg);
+  static warn(...msg: unknown[]) {
+    console.log(...msg);
   }
 }

+ 5 - 5
frontend/appflowy_tauri/src/appflowy_app/views/BoardPage.tsx

@@ -4,19 +4,19 @@ import { Board } from '../components/board/Board';
 
 export const BoardPage = () => {
   const params = useParams();
-  const [databaseId, setDatabaseId] = useState('');
+  const [viewId, setViewId] = useState('');
 
   useEffect(() => {
     if (params?.id?.length) {
-      // setDatabaseId(params.id);
-      setDatabaseId('testDb');
+      setViewId(params.id);
+      // setDatabaseId('testDb');
     }
   }, [params]);
 
   return (
     <div className='flex h-full flex-col gap-8 px-8 pt-8'>
-      <h1 className='text-4xl font-bold'>Board</h1>
-      {databaseId?.length && <Board />}
+      <h1 className='text-4xl font-bold'>Board: {viewId}</h1>
+      {viewId?.length && <Board viewId={viewId} />}
     </div>
   );
 };

+ 0 - 1
frontend/rust-lib/flowy-core/src/lib.rs

@@ -102,7 +102,6 @@ fn create_log_filter(level: String, with_crates: Vec<String>) -> String {
   filters.push(format!("dart_ffi={}", "info"));
   filters.push(format!("flowy_sqlite={}", "info"));
   filters.push(format!("flowy_net={}", "info"));
-
   #[cfg(feature = "profiling")]
   filters.push(format!("tokio={}", level));