Selaa lähdekoodia

fix: Tauri UI issues (#1980) (#1982)

* 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

* chore: add tauri clean

* chore: save offset top of nav items

* chore: move constants

* fix: nav item popup overflow

* fix: page rename position

* chore: remove offset top

* chore: remove floating menu functions

* chore: scroll down to new page

* chore: smooth scroll and scroll to new folder

* fix: breadcrumbs

* chore: back and forward buttons nav scroll fix

* chore: get board groups and rows

* chore: set log level & remove empty line

* fix: create kanban board row

* fix: appflowy session name

---------

Co-authored-by: ascarbek <[email protected]>
Nathan.fooo 2 vuotta sitten
vanhempi
commit
5a17716fd8
30 muutettua tiedostoa jossa 312 lisäystä ja 436 poistoa
  1. 1 1
      frontend/appflowy_tauri/package.json
  2. 17 17
      frontend/appflowy_tauri/src-tauri/src/init.rs
  3. 3 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/Popup.tsx
  4. 5 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/constants.ts
  5. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useCell.ts
  6. 13 14
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useDatabase.ts
  7. 0 55
      frontend/appflowy_tauri/src/appflowy_app/components/board/Board.hooks.ts
  8. 9 22
      frontend/appflowy_tauri/src/appflowy_app/components/board/Board.tsx
  9. 12 18
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardBlock.tsx
  10. 20 103
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCard.tsx
  11. 31 7
      frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/Breadcrumbs.tsx
  12. 1 2
      frontend/appflowy_tauri/src/appflowy_app/components/layout/MainPanel.tsx
  13. 24 9
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.hooks.ts
  14. 16 18
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.tsx
  15. 4 1
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItemOptionsPopup.tsx
  16. 0 79
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationFloatingPanel.tsx
  17. 2 38
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.hooks.ts
  18. 79 10
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx
  19. 8 2
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewFolderButton.tsx
  20. 4 1
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewPagePopup.tsx
  21. 12 3
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.hooks.ts
  22. 19 3
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.tsx
  23. 3 0
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/RenamePopup.tsx
  24. 2 17
      frontend/appflowy_tauri/src/appflowy_app/components/layout/Screen.tsx
  25. 13 4
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/database_bd_svc.ts
  26. 1 1
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/group/group_controller.ts
  27. 5 1
      frontend/appflowy_tauri/src/appflowy_app/stores/reducers/folders/slice.ts
  28. 1 6
      frontend/appflowy_tauri/src/appflowy_app/stores/reducers/pages/slice.ts
  29. 1 1
      frontend/rust-lib/dart-ffi/src/lib.rs
  30. 4 0
      frontend/rust-lib/flowy-core/src/lib.rs

+ 1 - 1
frontend/appflowy_tauri/package.json

@@ -9,7 +9,7 @@
     "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:errors": "tsc --noEmit",
     "test:prettier": "yarn prettier --list-different src",
     "tauri:clean": "cargo make --cwd .. tauri_clean",
     "tauri:dev": "tauri dev"

+ 17 - 17
frontend/appflowy_tauri/src-tauri/src/init.rs

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

+ 3 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/Popup.tsx

@@ -12,11 +12,13 @@ export const Popup = ({
   className = '',
   onOutsideClick,
   columns = 1,
+  style,
 }: {
   items: IPopupItem[];
   className: string;
   onOutsideClick?: () => void;
   columns?: 1 | 2 | 3;
+  style?: any;
 }) => {
   const ref = useRef<HTMLDivElement>(null);
   useOutsideClick(ref, () => onOutsideClick && onOutsideClick());
@@ -27,7 +29,7 @@ export const Popup = ({
   };
 
   return (
-    <div ref={ref} className={`${className} rounded-lg bg-white px-2 py-2 shadow-md`}>
+    <div ref={ref} className={`${className} rounded-lg bg-white px-2 py-2 shadow-md`} style={style}>
       <div
         className={`grid ${columns === 1 && 'grid-cols-1'} ${columns === 2 && 'grid-cols-2'} ${
           columns === 3 && 'grid-cols-3'

+ 5 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/constants.ts

@@ -0,0 +1,5 @@
+export const INITIAL_FOLDER_HEIGHT = 40;
+export const FOLDER_MARGIN = 16;
+export const PAGE_ITEM_HEIGHT = 40;
+export const ANIMATION_DURATION = 300;
+export const NAV_PANEL_MINIMUM_WIDTH = 200;

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

@@ -20,9 +20,9 @@ export const useCell = (cellIdentifier: CellIdentifier, cellCache: CellCache, fi
     // 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();
+      // dispose is causing an error
+      // void cellController.dispose();
     };
   }, []);
 

+ 13 - 14
frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useDatabase.ts

@@ -1,30 +1,26 @@
 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 { databaseActions, DatabaseFieldMap, IDatabaseColumn } from '../../../stores/reducers/database/slice';
+import { useAppDispatch } 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';
+import { ViewLayoutTypePB } from '@/services/backend';
+import { DatabaseGroupController } from '$app/stores/effects/database/group/group_controller';
 
-export const useDatabase = (viewId: string) => {
+export const useDatabase = (viewId: string, type?: ViewLayoutTypePB) => {
   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[]>([]);
+  const [groups, setGroups] = useState<readonly DatabaseGroupController[]>([]);
 
   useEffect(() => {
     if (!viewId.length) return;
     const c = new DatabaseController(viewId);
     setController(c);
 
-    // on unmount dispose the controller
-    return () => void c.dispose();
+    // dispose is causing an error
+    // return () => void c.dispose();
   }, [viewId]);
 
   const loadFields = async (fieldInfos: readonly FieldInfo[]) => {
@@ -45,7 +41,6 @@ export const useDatabase = (viewId: string) => {
 
     dispatch(databaseActions.updateFields({ fields }));
     dispatch(databaseActions.updateColumns({ columns }));
-    console.log(fields, columns);
   };
 
   useEffect(() => {
@@ -61,8 +56,12 @@ export const useDatabase = (viewId: string) => {
         },
       });
       await controller.open();
+
+      if (type === ViewLayoutTypePB.Board) {
+        setGroups(controller.groups.value);
+      }
     })();
   }, [controller]);
 
-  return { loadFields, controller, rows };
+  return { loadFields, controller, rows, groups };
 };

+ 0 - 55
frontend/appflowy_tauri/src/appflowy_app/components/board/Board.hooks.ts

@@ -1,55 +0,0 @@
-import { useEffect, useState } from 'react';
-import { useAppDispatch, useAppSelector } from '../../stores/store';
-import { boardActions } from '../../stores/reducers/board/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[]>([]);
-  const [movingRowId, setMovingRowId] = useState<string | undefined>(undefined);
-  const [ghostLocation, setGhostLocation] = useState<{ column: number; row: number }>({ column: 0, row: 0 });
-
-  useEffect(() => {
-    setTitle(database.title);
-    if (database.fields[groupingFieldId]) {
-      setBoardColumns(
-        (database.fields[groupingFieldId].fieldOptions as ISelectOptionType | undefined)?.selectOptions || []
-      );
-    }
-  }, [database, groupingFieldId]);
-
-  const changeGroupingField = (fieldId: string) => {
-    dispatch(
-      boardActions.setGroupingFieldId({
-        fieldId,
-      })
-    );
-  };
-
-  const onGhostItemMove = (columnIndex: number, rowIndex: number) => {
-    setGhostLocation({ column: columnIndex, row: rowIndex });
-  };
-
-  const startMove = (rowId: string) => {
-    setMovingRowId(rowId);
-  };
-
-  const endMove = () => {
-    setMovingRowId(undefined);
-  };
-
-  return {
-    title,
-    boardColumns,
-    groupingFieldId,
-    changeGroupingField,
-    startMove,
-    endMove,
-    onGhostItemMove,
-    movingRowId,
-    ghostLocation,
-  };
-};

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

@@ -2,29 +2,17 @@ import { SettingsSvg } from '../_shared/svg/SettingsSvg';
 import { SearchInput } from '../_shared/SearchInput';
 import { BoardBlock } from './BoardBlock';
 import { NewBoardBlock } from './NewBoardBlock';
-import { useBoard } from './Board.hooks';
 import { useDatabase } from '../_shared/database-hooks/useDatabase';
+import { ViewLayoutTypePB } from '@/services/backend';
 
 export const Board = ({ viewId }: { viewId: string }) => {
-  const { controller, rows } = useDatabase(viewId);
-
-  const {
-    title,
-    boardColumns,
-    groupingFieldId,
-    changeGroupingField,
-    startMove,
-    endMove,
-    onGhostItemMove,
-    movingRowId,
-    ghostLocation,
-  } = useBoard();
+  const { controller, rows, groups } = useDatabase(viewId, ViewLayoutTypePB.Board);
 
   return (
     <>
       <div className='flex w-full items-center justify-between'>
         <div className={'flex items-center text-xl font-semibold'}>
-          <div>{title}</div>
+          <div>{'Kanban'}</div>
           <button className={'ml-2 h-5 w-5'}>
             <SettingsSvg></SettingsSvg>
           </button>
@@ -37,16 +25,15 @@ export const Board = ({ viewId }: { viewId: string }) => {
       <div className={'relative w-full flex-1 overflow-auto'}>
         <div className={'absolute flex h-full flex-shrink-0 items-start justify-start gap-4'}>
           {controller &&
-            boardColumns?.map((column, index) => (
+            groups &&
+            groups.map((group, index) => (
               <BoardBlock
+                key={index}
                 viewId={viewId}
                 controller={controller}
-                key={index}
-                title={column.title}
-                rows={rows}
-                groupingFieldId={groupingFieldId}
-                startMove={startMove}
-                endMove={endMove}
+                rows={group.rows}
+                title={group.name}
+                allRows={rows}
               />
             ))}
 

+ 12 - 18
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardBlock.tsx

@@ -3,23 +3,20 @@ import AddSvg from '../_shared/svg/AddSvg';
 import { BoardCard } from './BoardCard';
 import { RowInfo } from '../../stores/effects/database/row/row_cache';
 import { DatabaseController } from '../../stores/effects/database/database_controller';
+import { RowPB } from '@/services/backend';
 
 export const BoardBlock = ({
   viewId,
   controller,
   title,
-  groupingFieldId,
   rows,
-  startMove,
-  endMove,
+  allRows,
 }: {
   viewId: string;
   controller: DatabaseController;
   title: string;
-  groupingFieldId: string;
-  rows: readonly RowInfo[];
-  startMove: (id: string) => void;
-  endMove: () => void;
+  rows: RowPB[];
+  allRows: readonly RowInfo[];
 }) => {
   return (
     <div className={'flex h-full w-[250px] flex-col rounded-lg bg-surface-1'}>
@@ -38,17 +35,14 @@ export const BoardBlock = ({
         </div>
       </div>
       <div className={'flex flex-1 flex-col gap-1 overflow-auto px-2'}>
-        {rows.map((row, index) => (
-          <BoardCard
-            viewId={viewId}
-            controller={controller}
-            key={index}
-            groupingFieldId={groupingFieldId}
-            row={row}
-            startMove={() => startMove(row.row.id)}
-            endMove={() => endMove()}
-          ></BoardCard>
-        ))}
+        {rows.map((row_pb, index) => {
+          const row = allRows.find((r) => r.row.id === row_pb.id);
+          return row ? (
+            <BoardCard viewId={viewId} controller={controller} key={index} rowInfo={row}></BoardCard>
+          ) : (
+            <span key={index}></span>
+          );
+        })}
       </div>
       <div className={'p-2'}>
         <button className={'flex w-full items-center gap-2 rounded-lg px-2 py-2 hover:bg-surface-2'}>

+ 20 - 103
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCard.tsx

@@ -1,121 +1,38 @@
-import { DatabaseFieldMap, IDatabaseColumn, IDatabaseRow } from '../../stores/reducers/database/slice';
 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 BoardCard = ({
   viewId,
   controller,
-  groupingFieldId,
-  // fields,
-  // columns,
-  row,
-  startMove,
-  endMove,
+  rowInfo,
 }: {
   viewId: string;
   controller: DatabaseController;
-  groupingFieldId: string;
-  // fields: DatabaseFieldMap;
-  // columns: IDatabaseColumn[];
-  row: RowInfo;
-  startMove: () => void;
-  endMove: () => void;
+  rowInfo: RowInfo;
 }) => {
-  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);
-  const [ghostHeight, setGhostHeight] = useState(0);
-  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();
-      setGhostWidth(width);
-      setGhostHeight(height);
-      setGhostLeft(left);
-      setGhostTop(top);
-
-      startMove();
-
-      const gEl = document.getElementById('ghost-block');
-      if (gEl?.innerHTML) {
-        gEl.innerHTML = el.current.innerHTML;
-      }
-    }
-  }, [el, isMoving]);
-
-  const onMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
-    setGhostLeft(ghostLeft + e.movementX);
-    setGhostTop(ghostTop + e.movementY);
-  };
-
-  const onMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
-    setIsMoving(false);
-    endMove();
-  };
-
-  const dragStart = () => {
-    if (isDown) {
-      setIsMoving(true);
-      setIsDown(false);
-    }
-  };
+  const { cells } = useRow(viewId, controller, rowInfo);
 
   return (
-    <>
-      <div
-        ref={el}
-        onMouseDown={() => setIsDown(true)}
-        onMouseMove={dragStart}
-        onMouseUp={() => setIsDown(false)}
-        onClick={() => console.log('on click')}
-        className={`relative cursor-pointer select-none rounded-lg border border-shade-6 bg-white px-3 py-2 transition-transform duration-100 hover:bg-main-selector `}
-      >
-        <button className={'absolute right-4 top-2.5 h-5 w-5 rounded hover:bg-surface-2'}>
-          <Details2Svg></Details2Svg>
-        </button>
-        <div className={'flex flex-col gap-3'}>
-          {cells.map((cell, index) => (
-            <BoardCell
-              key={index}
-              cellIdentifier={cell.cellIdentifier}
-              cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
-              fieldController={controller.fieldController}
-            ></BoardCell>
-          ))}
-        </div>
+    <div
+      onClick={() => console.log('on click')}
+      className={`relative cursor-pointer select-none rounded-lg border border-shade-6 bg-white px-3 py-2 transition-transform duration-100 hover:bg-main-selector `}
+    >
+      <button className={'absolute right-4 top-2.5 h-5 w-5 rounded hover:bg-surface-2'}>
+        <Details2Svg></Details2Svg>
+      </button>
+      <div className={'flex flex-col gap-3'}>
+        {cells.map((cell, index) => (
+          <BoardCell
+            key={index}
+            cellIdentifier={cell.cellIdentifier}
+            cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
+            fieldController={controller.fieldController}
+          ></BoardCell>
+        ))}
       </div>
-      {isMoving && (
-        <div
-          onMouseMove={onMouseMove}
-          onMouseUp={onMouseUp}
-          onMouseLeave={onMouseUp}
-          id={'ghost-block'}
-          className={
-            'fixed z-10 rotate-6 scale-105 cursor-pointer select-none rounded-lg border border-shade-6 bg-white px-3 py-2'
-          }
-          style={{
-            width: `${ghostWidth}px`,
-            height: `${ghostHeight}px`,
-            left: `${ghostLeft}px`,
-            top: `${ghostTop}px`,
-          }}
-        >
-          &nbsp;
-        </div>
-      )}
-    </>
+    </div>
   );
 };

+ 31 - 7
frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/Breadcrumbs.tsx

@@ -1,6 +1,30 @@
 import { ShowMenuSvg } from '../../_shared/svg/ShowMenuSvg';
+import { useEffect, useState } from 'react';
+import { useAppSelector } from '../../../stores/store';
+import { useLocation } from 'react-router-dom';
 
 export const Breadcrumbs = ({ menuHidden, onShowMenuClick }: { menuHidden: boolean; onShowMenuClick: () => void }) => {
+  const [folderName, setFolderName] = useState('');
+  const [pageName, setPageName] = useState('');
+  const [activePageId, setActivePageId] = useState<string>('');
+  const currentLocation = useLocation();
+  const pagesStore = useAppSelector((state) => state.pages);
+  const foldersStore = useAppSelector((state) => state.folders);
+
+  useEffect(() => {
+    const { pathname } = currentLocation;
+    const parts = pathname.split('/');
+    const pageId = parts[parts.length - 1];
+    setActivePageId(pageId);
+  }, [currentLocation]);
+
+  useEffect(() => {
+    const page = pagesStore.find((p) => p.id === activePageId);
+    const folder = foldersStore.find((f) => f.id === page?.folderId);
+    setFolderName(folder?.title || '');
+    setPageName(page?.title || '');
+  }, [pagesStore, foldersStore, activePageId]);
+
   return (
     <div className={'flex items-center'}>
       <div className={'mr-4 flex items-center'}>
@@ -11,16 +35,16 @@ export const Breadcrumbs = ({ menuHidden, onShowMenuClick }: { menuHidden: boole
         )}
 
         <button className={'p-1'} onClick={() => history.back()}>
-          <img src={'/images/home/arrow_left.svg'} />
+          <img src={'/images/home/arrow_left.svg'} alt={''} />
         </button>
-        <button className={'p-1'}>
-          <img src={'/images/home/arrow_right.svg'} />
+        <button className={'p-1'} onClick={() => history.forward()}>
+          <img src={'/images/home/arrow_right.svg'} alt={''} />
         </button>
       </div>
-      <div className={'flex items-center'}>
-        <span className={'mr-8'}>Getting Started</span>
-        <span className={'mr-8'}>/</span>
-        <span className={'mr-8'}>Read Me</span>
+      <div className={'mr-8 flex items-center gap-4'}>
+        <span>{folderName}</span>
+        <span>/</span>
+        <span>{pageName}</span>
       </div>
     </div>
   );

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

@@ -1,8 +1,7 @@
 import { ReactNode, useEffect, useState } from 'react';
 import { HeaderPanel } from './HeaderPanel/HeaderPanel';
 import { FooterPanel } from './FooterPanel';
-
-const ANIMATION_DURATION = 300;
+import { ANIMATION_DURATION } from '../_shared/constants';
 
 export const MainPanel = ({
   left,

+ 24 - 9
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.hooks.ts

@@ -7,15 +7,15 @@ import { AppBackendService } from '../../../stores/effects/folder/app/app_bd_svc
 import { WorkspaceBackendService } from '../../../stores/effects/folder/workspace/workspace_bd_svc';
 import { useError } from '../../error/Error.hooks';
 import { AppObserver } from '../../../stores/effects/folder/app/app_observer';
-
-const initialFolderHeight = 40;
-const initialPageHeight = 40;
-const animationDuration = 500;
+import { useNavigate } from 'react-router-dom';
+import { INITIAL_FOLDER_HEIGHT, PAGE_ITEM_HEIGHT } from '../../_shared/constants';
 
 export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
   const appDispatch = useAppDispatch();
   const workspace = useAppSelector((state) => state.workspace);
 
+  const navigate = useNavigate();
+
   // Actions
   const [showPages, setShowPages] = useState(false);
   const [showFolderOptions, setShowFolderOptions] = useState(false);
@@ -23,7 +23,7 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
   const [showRenamePopup, setShowRenamePopup] = useState(false);
 
   // UI configurations
-  const [folderHeight, setFolderHeight] = useState(`${initialFolderHeight}px`);
+  const [folderHeight, setFolderHeight] = useState(`${INITIAL_FOLDER_HEIGHT}px`);
 
   // Observers
   const appObserver = new AppObserver(folder.id);
@@ -58,15 +58,15 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
 
   useEffect(() => {
     if (showPages) {
-      setFolderHeight(`${initialFolderHeight + pages.length * initialPageHeight}px`);
+      setFolderHeight(`${INITIAL_FOLDER_HEIGHT + pages.length * PAGE_ITEM_HEIGHT}px`);
     }
   }, [pages]);
 
   const onFolderNameClick = () => {
     if (showPages) {
-      setFolderHeight(`${initialFolderHeight}px`);
+      setFolderHeight(`${INITIAL_FOLDER_HEIGHT}px`);
     } else {
-      setFolderHeight(`${initialFolderHeight + pages.length * initialPageHeight}px`);
+      setFolderHeight(`${INITIAL_FOLDER_HEIGHT + pages.length * PAGE_ITEM_HEIGHT}px`);
     }
     setShowPages(!showPages);
   };
@@ -140,6 +140,10 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
           id: newView.id,
         })
       );
+
+      setShowPages(true);
+
+      navigate(`/page/document/${newView.id}`);
     } catch (e: any) {
       error.showError(e?.message);
     }
@@ -153,6 +157,8 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
         layoutType: ViewLayoutTypePB.Board,
       });
 
+      setShowPages(true);
+
       appDispatch(
         pagesActions.addPage({
           folderId: folder.id,
@@ -161,6 +167,8 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
           id: newView.id,
         })
       );
+
+      navigate(`/page/board/${newView.id}`);
     } catch (e: any) {
       error.showError(e?.message);
     }
@@ -174,6 +182,8 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
         layoutType: ViewLayoutTypePB.Grid,
       });
 
+      setShowPages(true);
+
       appDispatch(
         pagesActions.addPage({
           folderId: folder.id,
@@ -182,11 +192,17 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
           id: newView.id,
         })
       );
+
+      navigate(`/page/grid/${newView.id}`);
     } catch (e: any) {
       error.showError(e?.message);
     }
   };
 
+  useEffect(() => {
+    appDispatch(foldersActions.setShowPages({ id: folder.id, showPages: showPages }));
+  }, [showPages]);
+
   return {
     showPages,
     onFolderNameClick,
@@ -208,6 +224,5 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
 
     closePopup,
     folderHeight,
-    animationDuration,
   };
 };

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

@@ -8,10 +8,9 @@ import { IPage } from '../../../stores/reducers/pages/slice';
 import { PageItem } from './PageItem';
 import { Button } from '../../_shared/Button';
 import { RenamePopup } from './RenamePopup';
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
 import { DropDownShowSvg } from '../../_shared/svg/DropDownShowSvg';
-
-let timeoutHandle: any;
+import { ANIMATION_DURATION } from '../../_shared/constants';
 
 export const FolderItem = ({
   folder,
@@ -43,28 +42,24 @@ export const FolderItem = ({
 
     closePopup,
     folderHeight,
-    animationDuration,
   } = useFolderEvents(folder, pages);
 
-  const [hideOverflow, setHideOverflow] = useState(!showPages);
+  const [popupY, setPopupY] = useState(0);
+
+  const el = useRef<HTMLDivElement>(null);
 
   useEffect(() => {
-    clearTimeout(timeoutHandle);
-    if (showPages) {
-      timeoutHandle = setTimeout(() => {
-        setHideOverflow(!showPages);
-      }, animationDuration);
-    } else {
-      setHideOverflow(!showPages);
+    if (el.current) {
+      const { top } = el.current.getBoundingClientRect();
+      setPopupY(top);
     }
-  }, [showPages]);
+  }, [showFolderOptions, showNewPageOptions, showRenamePopup]);
 
   return (
-    /*transitionTimingFunction:'cubic-bezier(.36,1.55,.65,1.1)'*/
-    <div className={'relative'}>
+    <div ref={el}>
       <div
-        className={`relative my-2 ${hideOverflow ? 'overflow-hidden' : ''} transition-all `}
-        style={{ height: folderHeight, transitionDuration: `${animationDuration}ms` }}
+        className={`my-2 overflow-hidden transition-all`}
+        style={{ height: folderHeight, transitionDuration: `${ANIMATION_DURATION}ms` }}
       >
         <div
           onClick={() => onFolderNameClick()}
@@ -78,7 +73,7 @@ export const FolderItem = ({
               {folder.title}
             </span>
           </button>
-          <div className={'relative flex items-center'}>
+          <div className={'flex items-center'}>
             <Button size={'box-small-transparent'} onClick={() => onFolderOptionsClick()}>
               <Details2Svg></Details2Svg>
             </Button>
@@ -98,6 +93,7 @@ export const FolderItem = ({
           onDeleteClick={() => deleteFolder()}
           onDuplicateClick={() => duplicateFolder()}
           onClose={() => closePopup()}
+          top={popupY - 124 + 40}
         ></NavItemOptionsPopup>
       )}
       {showNewPageOptions && (
@@ -106,6 +102,7 @@ export const FolderItem = ({
           onBoardClick={() => onAddNewBoardPage()}
           onGridClick={() => onAddNewGridPage()}
           onClose={() => closePopup()}
+          top={popupY - 124 + 40}
         ></NewPagePopup>
       )}
       {showRenamePopup && (
@@ -113,6 +110,7 @@ export const FolderItem = ({
           value={folder.title}
           onChange={(newTitle) => changeFolderTitle(newTitle)}
           onClose={closeRenamePopup}
+          top={popupY - 124 + 40}
         ></RenamePopup>
       )}
     </div>

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

@@ -8,11 +8,13 @@ export const NavItemOptionsPopup = ({
   onDeleteClick,
   onDuplicateClick,
   onClose,
+  top,
 }: {
   onRenameClick: () => void;
   onDeleteClick: () => void;
   onDuplicateClick: () => void;
   onClose?: () => void;
+  top: number;
 }) => {
   const items: IPopupItem[] = [
     {
@@ -48,7 +50,8 @@ export const NavItemOptionsPopup = ({
     <Popup
       onOutsideClick={() => onClose && onClose()}
       items={items}
-      className={'absolute right-0 top-[40px] z-10'}
+      className={`absolute right-0`}
+      style={{ top: `${top}px` }}
     ></Popup>
   );
 };

+ 0 - 79
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationFloatingPanel.tsx

@@ -1,79 +0,0 @@
-import { AppLogo } from '../AppLogo';
-import { WorkspaceUser } from '../WorkspaceUser';
-import { FolderItem } from './FolderItem';
-import { PluginsButton } from './PluginsButton';
-import { TrashButton } from './TrashButton';
-import { NewFolderButton } from './NewFolderButton';
-import { IFolder } from '../../../stores/reducers/folders/slice';
-import { IPage } from '../../../stores/reducers/pages/slice';
-import { useEffect, useRef, useState } from 'react';
-
-const animationDuration = 500;
-
-export const NavigationFloatingPanel = ({
-  onFixNavigationClick,
-  slideInFloatingPanel,
-  folders,
-  pages,
-  onPageClick,
-  setWidth,
-}: {
-  onFixNavigationClick: () => void;
-  slideInFloatingPanel: boolean;
-  folders: IFolder[];
-  pages: IPage[];
-  onPageClick: (page: IPage) => void;
-  setWidth: (v: number) => void;
-}) => {
-  const el = useRef<HTMLDivElement>(null);
-  const [panelLeft, setPanelLeft] = useState(0);
-
-  useEffect(() => {
-    if (!el?.current) return;
-
-    const { width } = el.current.getBoundingClientRect();
-    setWidth(width);
-
-    if (slideInFloatingPanel) {
-      setPanelLeft(0);
-    } else {
-      setPanelLeft(-width);
-    }
-  }, [el.current, slideInFloatingPanel]);
-
-  return (
-    <div
-      ref={el}
-      className={
-        'fixed top-16 z-10 flex flex-col justify-between rounded-tr rounded-br border border-l-0 border-shade-4 bg-white text-sm shadow-md transition-all'
-      }
-      style={{ left: panelLeft, transitionDuration: `${animationDuration}ms` }}
-    >
-      <div className={'flex flex-col'}>
-        <AppLogo iconToShow={'show'} onShowMenuClick={onFixNavigationClick}></AppLogo>
-
-        <WorkspaceUser></WorkspaceUser>
-
-        <div className={'flex flex-col px-2'}>
-          {folders.map((folder, index) => (
-            <FolderItem
-              key={index}
-              folder={folder}
-              pages={pages.filter((page) => page.folderId === folder.id)}
-              onPageClick={onPageClick}
-            ></FolderItem>
-          ))}
-        </div>
-      </div>
-
-      <div className={'flex flex-col'}>
-        <div className={'border-b border-shade-6 px-2 pb-4'}>
-          <PluginsButton></PluginsButton>
-          <TrashButton></TrashButton>
-        </div>
-
-        <NewFolderButton></NewFolderButton>
-      </div>
-    </div>
-  );
-};

+ 2 - 38
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.hooks.ts

@@ -1,36 +1,17 @@
-import { useAppDispatch, useAppSelector } from '../../../stores/store';
+import { 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;
+import { useState } from 'react';
 
 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();
 
-  const onCollapseNavigationClick = () => {
-    setSlideInFloatingPanel(true);
-    setNavigationPanelFixed(false);
-  };
-
-  const onFixNavigationClick = () => {
-    setNavigationPanelFixed(true);
-  };
-
-  const [floatingPanelWidth, setFloatingPanelWidth] = useState(0);
-
   const onHideMenuClick = () => {
     setMenuHidden(true);
   };
@@ -54,31 +35,14 @@ export const useNavigationPanelHooks = function () {
       }
     })();
 
-    dispatch(activePageIdActions.setActivePageId(page.id));
-
     navigate(`/page/${pageTypeRoute}/${page.id}`);
   };
 
-  const onScreenMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
-    if (e.screenX <= FLOATING_PANEL_SHOW_WIDTH) {
-      setSlideInFloatingPanel(true);
-    } else if (e.screenX > floatingPanelWidth + FLOATING_PANEL_HIDE_EXTRA_WIDTH) {
-      setSlideInFloatingPanel(false);
-    }
-  };
-
   return {
     width,
     folders,
     pages,
-    navigate,
     onPageClick,
-    onCollapseNavigationClick,
-    onFixNavigationClick,
-    navigationPanelFixed,
-    onScreenMouseMove,
-    slideInFloatingPanel,
-    setFloatingPanelWidth,
     menuHidden,
     onHideMenuClick,
     onShowMenuClick,

+ 79 - 10
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx

@@ -6,11 +6,16 @@ import { NewFolderButton } from './NewFolderButton';
 import { NavigationResizer } from './NavigationResizer';
 import { IFolder } from '../../../stores/reducers/folders/slice';
 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;
+import { useLocation, useNavigate } from 'react-router-dom';
+import React, { useEffect, useRef, useState } from 'react';
+import { useAppSelector } from '../../../stores/store';
+import {
+  ANIMATION_DURATION,
+  FOLDER_MARGIN,
+  INITIAL_FOLDER_HEIGHT,
+  NAV_PANEL_MINIMUM_WIDTH,
+  PAGE_ITEM_HEIGHT,
+} from '../../_shared/constants';
 
 export const NavigationPanel = ({
   onHideMenuClick,
@@ -27,6 +32,66 @@ export const NavigationPanel = ({
   pages: IPage[];
   onPageClick: (page: IPage) => void;
 }) => {
+  const el = useRef<HTMLDivElement>(null);
+  const foldersStore = useAppSelector((state) => state.folders);
+  const pagesStore = useAppSelector((state) => state.pages);
+  const [activePageId, setActivePageId] = useState<string>('');
+  const currentLocation = useLocation();
+  const [maxHeight, setMaxHeight] = useState(0);
+
+  useEffect(() => {
+    const { pathname } = currentLocation;
+    const parts = pathname.split('/');
+    const pageId = parts[parts.length - 1];
+    setActivePageId(pageId);
+  }, [currentLocation]);
+
+  useEffect(() => {
+    setTimeout(() => {
+      if (!el.current) return;
+      if (!activePageId?.length) return;
+      const activePage = pagesStore.find((page) => page.id === activePageId);
+      if (!activePage) return;
+
+      const folderIndex = foldersStore.findIndex((folder) => folder.id === activePage.folderId);
+      if (folderIndex === -1) return;
+
+      let height = 0;
+      for (let i = 0; i < folderIndex; i++) {
+        height += INITIAL_FOLDER_HEIGHT + FOLDER_MARGIN;
+        if (foldersStore[i].showPages === true) {
+          height += pagesStore.filter((p) => p.folderId === foldersStore[i].id).length * PAGE_ITEM_HEIGHT;
+        }
+      }
+
+      height += INITIAL_FOLDER_HEIGHT + FOLDER_MARGIN / 2;
+
+      const pageIndex = pagesStore
+        .filter((p) => p.folderId === foldersStore[folderIndex].id)
+        .findIndex((p) => p.id === activePageId);
+      for (let i = 0; i <= pageIndex; i++) {
+        height += PAGE_ITEM_HEIGHT;
+      }
+
+      const elHeight = el.current.getBoundingClientRect().height;
+      const scrollTop = el.current.scrollTop;
+
+      if (scrollTop + elHeight < height || scrollTop > height) {
+        el.current.scrollTo({ top: height - elHeight, behavior: 'smooth' });
+      }
+    }, ANIMATION_DURATION);
+  }, [activePageId]);
+
+  useEffect(() => {
+    setMaxHeight(foldersStore.length * (INITIAL_FOLDER_HEIGHT + FOLDER_MARGIN) + pagesStore.length * PAGE_ITEM_HEIGHT);
+  }, [foldersStore, pagesStore]);
+
+  const scrollDown = () => {
+    setTimeout(() => {
+      el?.current?.scrollTo({ top: maxHeight, behavior: 'smooth' });
+    }, ANIMATION_DURATION);
+  };
+
   return (
     <>
       <div
@@ -40,7 +105,11 @@ export const NavigationPanel = ({
         <div className={'flex flex-col'}>
           <AppLogo iconToShow={'hide'} onHideMenuClick={onHideMenuClick}></AppLogo>
           <WorkspaceUser></WorkspaceUser>
-          <WorkspaceApps folders={folders} pages={pages} onPageClick={onPageClick} />
+          <div className={'relative flex flex-col'} style={{ height: 'calc(100vh - 300px)' }}>
+            <div className={'flex flex-col overflow-auto px-2'} ref={el}>
+              <WorkspaceApps folders={folders} pages={pages} onPageClick={onPageClick} />
+            </div>
+          </div>
         </div>
 
         <div className={'flex flex-col'}>
@@ -55,10 +124,10 @@ export const NavigationPanel = ({
           </div>
 
           {/*New Folder Button*/}
-          <NewFolderButton></NewFolderButton>
+          <NewFolderButton scrollDown={scrollDown}></NewFolderButton>
         </div>
       </div>
-      <NavigationResizer minWidth={MINIMUM_WIDTH}></NavigationResizer>
+      <NavigationResizer minWidth={NAV_PANEL_MINIMUM_WIDTH}></NavigationResizer>
     </>
   );
 };
@@ -70,7 +139,7 @@ type AppsContext = {
 };
 
 const WorkspaceApps: React.FC<AppsContext> = ({ folders, pages, onPageClick }) => (
-  <div className={'flex flex-col overflow-auto px-2'} style={{ height: 'calc(100vh - 300px)' }}>
+  <>
     {folders.map((folder, index) => (
       <FolderItem
         key={index}
@@ -79,7 +148,7 @@ const WorkspaceApps: React.FC<AppsContext> = ({ folders, pages, onPageClick }) =
         onPageClick={onPageClick}
       ></FolderItem>
     ))}
-  </div>
+  </>
 );
 
 export const TestBackendButton = () => {

+ 8 - 2
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewFolderButton.tsx

@@ -1,11 +1,17 @@
 import AddSvg from '../../_shared/svg/AddSvg';
 import { useNewFolder } from './NewFolderButton.hooks';
 
-export const NewFolderButton = () => {
+export const NewFolderButton = ({ scrollDown }: { scrollDown: () => void }) => {
   const { onNewFolder } = useNewFolder();
 
   return (
-    <button onClick={() => onNewFolder()} className={'flex h-[50px] w-full items-center px-6 hover:bg-surface-2'}>
+    <button
+      onClick={() => {
+        void onNewFolder();
+        scrollDown();
+      }}
+      className={'flex h-[50px] w-full items-center px-6 hover:bg-surface-2'}
+    >
       <div className={'mr-2 rounded-full bg-main-accent text-white'}>
         <div className={'h-[24px] w-[24px] text-white'}>
           <AddSvg></AddSvg>

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

@@ -8,11 +8,13 @@ export const NewPagePopup = ({
   onGridClick,
   onBoardClick,
   onClose,
+  top,
 }: {
   onDocumentClick: () => void;
   onGridClick: () => void;
   onBoardClick: () => void;
   onClose?: () => void;
+  top: number;
 }) => {
   const items: IPopupItem[] = [
     {
@@ -48,7 +50,8 @@ export const NewPagePopup = ({
     <Popup
       onOutsideClick={() => onClose && onClose()}
       items={items}
-      className={'absolute right-0 top-[40px] z-10'}
+      className={'absolute right-0'}
+      style={{ top: `${top}px` }}
     ></Popup>
   );
 };

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

@@ -1,18 +1,27 @@
 import { IPage, pagesActions } from '../../../stores/reducers/pages/slice';
-import { useAppDispatch, useAppSelector } from '../../../stores/store';
-import { useState } from 'react';
+import { useAppDispatch } from '../../../stores/store';
+import { useEffect, useState } from 'react';
 import { nanoid } from 'nanoid';
 import { ViewBackendService } from '../../../stores/effects/folder/view/view_bd_svc';
 import { useError } from '../../error/Error.hooks';
+import { useLocation } from 'react-router-dom';
 
 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 [activePageId, setActivePageId] = useState<string>('');
+  const currentLocation = useLocation();
   const viewBackendService: ViewBackendService = new ViewBackendService(page.id);
   const error = useError();
 
+  useEffect(() => {
+    const { pathname } = currentLocation;
+    const parts = pathname.split('/');
+    const pageId = parts[parts.length - 1];
+    setActivePageId(pageId);
+  }, [currentLocation]);
+
   const onPageOptionsClick = () => {
     setShowPageOptions(!showPageOptions);
   };

+ 19 - 3
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.tsx

@@ -8,6 +8,8 @@ import { Button } from '../../_shared/Button';
 import { usePageEvents } from './PageItem.hooks';
 import { RenamePopup } from './RenamePopup';
 import { ViewLayoutTypePB } from '../../../../services/backend';
+import { useEffect, useRef, useState } from 'react';
+import { PAGE_ITEM_HEIGHT } from '../../_shared/constants';
 
 export const PageItem = ({ page, onPageClick }: { page: IPage; onPageClick: () => void }) => {
   const {
@@ -23,13 +25,25 @@ export const PageItem = ({ page, onPageClick }: { page: IPage; onPageClick: () =
     activePageId,
   } = usePageEvents(page);
 
+  const el = useRef<HTMLDivElement>(null);
+
+  const [popupY, setPopupY] = useState(0);
+
+  useEffect(() => {
+    if (el.current) {
+      const { top } = el.current.getBoundingClientRect();
+      setPopupY(top);
+    }
+  }, [showPageOptions, showRenamePopup]);
+
   return (
-    <div className={'relative'}>
+    <div ref={el}>
       <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 pl-8 pr-4 hover:bg-surface-2 ${
           activePageId === page.id ? 'bg-surface-2' : ''
         }`}
+        style={{ height: PAGE_ITEM_HEIGHT }}
       >
         <button className={'flex min-w-0 flex-1 items-center'}>
           <i className={'ml-1 mr-1 h-[16px] w-[16px]'}>
@@ -41,7 +55,7 @@ export const PageItem = ({ page, onPageClick }: { page: IPage; onPageClick: () =
             {page.title}
           </span>
         </button>
-        <div className={'relative flex items-center'}>
+        <div className={'flex items-center'}>
           <Button size={'box-small-transparent'} onClick={() => onPageOptionsClick()}>
             <Details2Svg></Details2Svg>
           </Button>
@@ -53,6 +67,7 @@ export const PageItem = ({ page, onPageClick }: { page: IPage; onPageClick: () =
           onDeleteClick={() => deletePage()}
           onDuplicateClick={() => duplicatePage()}
           onClose={() => closePopup()}
+          top={popupY - 124 + 40}
         ></NavItemOptionsPopup>
       )}
       {showRenamePopup && (
@@ -60,6 +75,7 @@ export const PageItem = ({ page, onPageClick }: { page: IPage; onPageClick: () =
           value={page.title}
           onChange={(newTitle) => changePageTitle(newTitle)}
           onClose={closeRenamePopup}
+          top={popupY - 124 + 40}
         ></RenamePopup>
       )}
     </div>

+ 3 - 0
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/RenamePopup.tsx

@@ -6,11 +6,13 @@ export const RenamePopup = ({
   onChange,
   onClose,
   className = '',
+  top,
 }: {
   value: string;
   onChange: (newTitle: string) => void;
   onClose: () => void;
   className?: string;
+  top?: number;
 }) => {
   const ref = useRef<HTMLDivElement>(null);
   const inputRef = useRef<HTMLInputElement>(null);
@@ -32,6 +34,7 @@ export const RenamePopup = ({
       className={
         'absolute left-[50px] top-[40px] z-10 flex w-[300px] rounded bg-white py-1 px-1.5 shadow-md ' + className
       }
+      style={{ top: `${top}px` }}
     >
       <input
         ref={inputRef}

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

@@ -2,7 +2,6 @@ import React, { ReactNode, useEffect } from 'react';
 import { NavigationPanel } from './NavigationPanel/NavigationPanel';
 import { MainPanel } from './MainPanel';
 import { useNavigationPanelHooks } from './NavigationPanel/NavigationPanel.hooks';
-import { NavigationFloatingPanel } from './NavigationPanel/NavigationFloatingPanel';
 import { useWorkspace } from './Workspace.hooks';
 import { useAppSelector } from '../../stores/store';
 
@@ -15,24 +14,10 @@ export const Screen = ({ children }: { children: ReactNode }) => {
     })();
   }, [currentUser.isAuthenticated]);
 
-  const {
-    width,
-    folders,
-    pages,
-    onPageClick,
-    onCollapseNavigationClick,
-    onFixNavigationClick,
-    navigationPanelFixed,
-    onScreenMouseMove,
-    slideInFloatingPanel,
-    setFloatingPanelWidth,
-    onHideMenuClick,
-    onShowMenuClick,
-    menuHidden,
-  } = useNavigationPanelHooks();
+  const { width, folders, pages, onPageClick, onHideMenuClick, onShowMenuClick, menuHidden } = useNavigationPanelHooks();
 
   return (
-    <div onMouseMove={onScreenMouseMove} className='flex h-screen w-screen bg-white text-black'>
+    <div className='flex h-screen w-screen bg-white text-black'>
       <NavigationPanel
         onHideMenuClick={onHideMenuClick}
         width={width}

+ 13 - 4
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/database_bd_svc.ts

@@ -39,10 +39,19 @@ export class DatabaseBackendService {
     return FolderEventCloseView(payload);
   };
 
-  createRow = async (rowId?: string, groupId?: string) => {
-    const payload = CreateRowPayloadPB.fromObject({ view_id: this.viewId, start_row_id: rowId ?? undefined });
-    if (groupId !== undefined) {
-      payload.group_id = groupId;
+  /// Create a row in database
+  /// 1.The row will be the last row in database if the params is undefined
+  /// 2.The row will be placed after the passed-in rowId
+  /// 3.The row will be moved to the group with groupId. Currently, grouping is
+  /// only support in kanban board.
+  createRow = async (params?: { rowId?: string; groupId?: string }) => {
+    const payload = CreateRowPayloadPB.fromObject({ view_id: this.viewId });
+    if (params?.rowId !== undefined) {
+      payload.start_row_id = params.rowId;
+    }
+
+    if (params?.groupId !== undefined) {
+      payload.group_id = params.groupId;
     }
     return DatabaseEventCreateRow(payload);
   };

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/group/group_controller.ts

@@ -94,7 +94,7 @@ export class DatabaseGroupController {
   };
 
   createRow = async () => {
-    return this.databaseBackendSvc.createRow(this.group.group_id);
+    return this.databaseBackendSvc.createRow({ groupId: this.group.group_id });
   };
 
   subscribe = (callbacks: GroupDataCallbacks) => {

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

@@ -3,6 +3,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
 export interface IFolder {
   id: string;
   title: string;
+  showPages?: boolean;
 }
 
 const initialState: IFolder[] = [];
@@ -15,7 +16,7 @@ export const foldersSlice = createSlice({
       state.push(action.payload);
     },
     renameFolder(state, action: PayloadAction<{ id: string; newTitle: string }>) {
-      return state.map((f) => (f.id === action.payload.id ? { id: f.id, title: action.payload.newTitle } : f));
+      return state.map((f) => (f.id === action.payload.id ? { ...f, title: action.payload.newTitle } : f));
     },
     deleteFolder(state, action: PayloadAction<{ id: string }>) {
       return state.filter((f) => f.id !== action.payload.id);
@@ -23,6 +24,9 @@ export const foldersSlice = createSlice({
     clearFolders() {
       return [];
     },
+    setShowPages(state, action: PayloadAction<{ id: string; showPages: boolean }>) {
+      return state.map((f) => (f.id === action.payload.id ? { ...f, showPages: action.payload.showPages } : f));
+    },
   },
 });
 

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

@@ -15,12 +15,7 @@ export const pagesSlice = createSlice({
   initialState: initialState,
   reducers: {
     didReceivePages(state, action: PayloadAction<IPage[]>) {
-      action.payload.forEach((updatedPage) => {
-        const index = state.findIndex((page) => page.id === updatedPage.id);
-        if (index !== -1) {
-          state.splice(index, 1, updatedPage);
-        }
-      });
+      return action.payload;
     },
     addPage(state, action: PayloadAction<IPage>) {
       state.push(action.payload);

+ 1 - 1
frontend/rust-lib/dart-ffi/src/lib.rs

@@ -30,7 +30,7 @@ pub extern "C" fn init_sdk(path: *mut c_char) -> i64 {
 
   let server_config = get_client_server_configuration().unwrap();
   let log_crates = vec!["flowy-ffi".to_string()];
-  let config = AppFlowyCoreConfig::new(path, "appflowy".to_string(), server_config)
+  let config = AppFlowyCoreConfig::new(path, DEFAULT_NAME.to_string(), server_config)
     .log_filter("info", log_crates);
   *APPFLOWY_CORE.write() = Some(AppFlowyCore::new(config));
 

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

@@ -33,6 +33,10 @@ use user_model::UserProfile;
 
 static INIT_LOG: AtomicBool = AtomicBool::new(false);
 
+/// This name will be used as to identify the current [AppFlowyCore] instance.
+/// Don't change this.
+pub const DEFAULT_NAME: &str = "appflowy";
+
 #[derive(Clone)]
 pub struct AppFlowyCoreConfig {
   /// Different `AppFlowyCoreConfig` instance should have different name