From 5a17716fd82b4e55d25cb12da80b7ff28931cab8 Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Mon, 13 Mar 2023 16:42:45 +0800 Subject: [PATCH] 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 --- frontend/appflowy_tauri/package.json | 2 +- frontend/appflowy_tauri/src-tauri/src/init.rs | 34 ++--- .../appflowy_app/components/_shared/Popup.tsx | 4 +- .../components/_shared/constants.ts | 5 + .../_shared/database-hooks/useCell.ts | 4 +- .../_shared/database-hooks/useDatabase.ts | 27 ++-- .../components/board/Board.hooks.ts | 55 -------- .../appflowy_app/components/board/Board.tsx | 31 ++--- .../components/board/BoardBlock.tsx | 30 ++--- .../components/board/BoardCard.tsx | 123 +++--------------- .../layout/HeaderPanel/Breadcrumbs.tsx | 38 +++++- .../components/layout/MainPanel.tsx | 3 +- .../NavigationPanel/FolderItem.hooks.ts | 33 +++-- .../layout/NavigationPanel/FolderItem.tsx | 34 +++-- .../NavigationPanel/NavItemOptionsPopup.tsx | 5 +- .../NavigationFloatingPanel.tsx | 79 ----------- .../NavigationPanel/NavigationPanel.hooks.ts | 40 +----- .../NavigationPanel/NavigationPanel.tsx | 89 +++++++++++-- .../NavigationPanel/NewFolderButton.tsx | 10 +- .../layout/NavigationPanel/NewPagePopup.tsx | 5 +- .../layout/NavigationPanel/PageItem.hooks.ts | 15 ++- .../layout/NavigationPanel/PageItem.tsx | 22 +++- .../layout/NavigationPanel/RenamePopup.tsx | 3 + .../appflowy_app/components/layout/Screen.tsx | 19 +-- .../effects/database/database_bd_svc.ts | 17 ++- .../database/group/group_controller.ts | 2 +- .../stores/reducers/folders/slice.ts | 6 +- .../stores/reducers/pages/slice.ts | 7 +- frontend/rust-lib/dart-ffi/src/lib.rs | 2 +- frontend/rust-lib/flowy-core/src/lib.rs | 4 + 30 files changed, 312 insertions(+), 436 deletions(-) create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/_shared/constants.ts delete mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/board/Board.hooks.ts delete mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationFloatingPanel.tsx diff --git a/frontend/appflowy_tauri/package.json b/frontend/appflowy_tauri/package.json index 18ec8c8de9..8ad6f3a410 100644 --- a/frontend/appflowy_tauri/package.json +++ b/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" diff --git a/frontend/appflowy_tauri/src-tauri/src/init.rs b/frontend/appflowy_tauri/src-tauri/src/init.rs index 98ab6d5376..a09c80c875 100644 --- a/frontend/appflowy_tauri/src-tauri/src/init.rs +++ b/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) } diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/Popup.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/Popup.tsx index a781437bda..d317322e17 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/Popup.tsx +++ b/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(null); useOutsideClick(ref, () => onOutsideClick && onOutsideClick()); @@ -27,7 +29,7 @@ export const Popup = ({ }; return ( -
+
{ - void cellController.dispose(); + // dispose is causing an error + // void cellController.dispose(); }; }, []); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useDatabase.ts b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useDatabase.ts index b3fc047ed5..4d4d89c9c5 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useDatabase.ts +++ b/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(); const [rows, setRows] = useState([]); + const [groups, setGroups] = useState([]); 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 }; }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/board/Board.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/board/Board.hooks.ts deleted file mode 100644 index f02a16d34f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/board/Board.hooks.ts +++ /dev/null @@ -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([]); - const [movingRowId, setMovingRowId] = useState(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, - }; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/board/Board.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/board/Board.tsx index 3869138342..ccfbb1ecfe 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/board/Board.tsx +++ b/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 ( <>
-
{title}
+
{'Kanban'}
@@ -37,16 +25,15 @@ export const Board = ({ viewId }: { viewId: string }) => {
{controller && - boardColumns?.map((column, index) => ( + groups && + groups.map((group, index) => ( ))} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/board/BoardBlock.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/board/BoardBlock.tsx index 29bc1bc2a1..633a87af3a 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/board/BoardBlock.tsx +++ b/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 (
@@ -38,17 +35,14 @@ export const BoardBlock = ({
- {rows.map((row, index) => ( - startMove(row.row.id)} - endMove={() => endMove()} - > - ))} + {rows.map((row_pb, index) => { + const row = allRows.find((r) => r.row.id === row_pb.id); + return row ? ( + + ) : ( + + ); + })}
-
- {cells.map((cell, index) => ( - - ))} -
+
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 `} + > + +
+ {cells.map((cell, index) => ( + + ))}
- {isMoving && ( -
-   -
- )} - +
); }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/Breadcrumbs.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/Breadcrumbs.tsx index e4258f30b3..9e5e608848 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/Breadcrumbs.tsx +++ b/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(''); + 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 (
@@ -11,16 +35,16 @@ export const Breadcrumbs = ({ menuHidden, onShowMenuClick }: { menuHidden: boole )} -
-
- Getting Started - / - Read Me +
+ {folderName} + / + {pageName}
); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/MainPanel.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/MainPanel.tsx index b181693fd1..d69ecb90e5 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/MainPanel.tsx +++ b/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, diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.hooks.ts index 81f762c298..3c38ab70cd 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.hooks.ts +++ b/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, }; }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.tsx index a4db96f52e..c1d87f2a5b 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.tsx +++ b/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(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)'*/ -
+
onFolderNameClick()} @@ -78,7 +73,7 @@ export const FolderItem = ({ {folder.title} -
+
@@ -98,6 +93,7 @@ export const FolderItem = ({ onDeleteClick={() => deleteFolder()} onDuplicateClick={() => duplicateFolder()} onClose={() => closePopup()} + top={popupY - 124 + 40} > )} {showNewPageOptions && ( @@ -106,6 +102,7 @@ export const FolderItem = ({ onBoardClick={() => onAddNewBoardPage()} onGridClick={() => onAddNewGridPage()} onClose={() => closePopup()} + top={popupY - 124 + 40} > )} {showRenamePopup && ( @@ -113,6 +110,7 @@ export const FolderItem = ({ value={folder.title} onChange={(newTitle) => changeFolderTitle(newTitle)} onClose={closeRenamePopup} + top={popupY - 124 + 40} > )}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItemOptionsPopup.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItemOptionsPopup.tsx index 8e195b052a..f167623399 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItemOptionsPopup.tsx +++ b/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 = ({ onClose && onClose()} items={items} - className={'absolute right-0 top-[40px] z-10'} + className={`absolute right-0`} + style={{ top: `${top}px` }} > ); }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationFloatingPanel.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationFloatingPanel.tsx deleted file mode 100644 index bada0dcc4e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationFloatingPanel.tsx +++ /dev/null @@ -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(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 ( -
-
- - - - -
- {folders.map((folder, index) => ( - page.folderId === folder.id)} - onPageClick={onPageClick} - > - ))} -
-
- -
-
- - -
- - -
-
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.hooks.ts index a370f18f4d..1387291293 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.hooks.ts +++ b/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 = (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, diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx index ca260a1574..e3528ca1f0 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx +++ b/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(null); + const foldersStore = useAppSelector((state) => state.folders); + const pagesStore = useAppSelector((state) => state.pages); + const [activePageId, setActivePageId] = useState(''); + 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 ( <>
- +
+
+ +
+
@@ -55,10 +124,10 @@ export const NavigationPanel = ({
{/*New Folder Button*/} - +
- + ); }; @@ -70,7 +139,7 @@ type AppsContext = { }; const WorkspaceApps: React.FC = ({ folders, pages, onPageClick }) => ( -
+ <> {folders.map((folder, index) => ( = ({ folders, pages, onPageClick }) = onPageClick={onPageClick} > ))} -
+ ); export const TestBackendButton = () => { diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewFolderButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewFolderButton.tsx index 4c487796c7..7c1df82c26 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewFolderButton.tsx +++ b/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 ( - -
+
@@ -53,6 +67,7 @@ export const PageItem = ({ page, onPageClick }: { page: IPage; onPageClick: () = onDeleteClick={() => deletePage()} onDuplicateClick={() => duplicatePage()} onClose={() => closePopup()} + top={popupY - 124 + 40} > )} {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} > )}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/RenamePopup.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/RenamePopup.tsx index e5e3a893e9..1efe2387e4 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/RenamePopup.tsx +++ b/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(null); const inputRef = useRef(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` }} > { })(); }, [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 ( -
+
{ - 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); }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/group/group_controller.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/group/group_controller.ts index 5077c3c017..1063cd7642 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/group/group_controller.ts +++ b/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) => { diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/folders/slice.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/folders/slice.ts index bae86cdfd0..80d93d596f 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/folders/slice.ts +++ b/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)); + }, }, }); diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/pages/slice.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/pages/slice.ts index 330eeb5968..9c7700d11b 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/pages/slice.ts +++ b/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) { - 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) { state.push(action.payload); diff --git a/frontend/rust-lib/dart-ffi/src/lib.rs b/frontend/rust-lib/dart-ffi/src/lib.rs index c3bd609d6d..d05c5d6445 100644 --- a/frontend/rust-lib/dart-ffi/src/lib.rs +++ b/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)); diff --git a/frontend/rust-lib/flowy-core/src/lib.rs b/frontend/rust-lib/flowy-core/src/lib.rs index 08ce100d7f..0ecdc807a6 100644 --- a/frontend/rust-lib/flowy-core/src/lib.rs +++ b/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