From 6a0d08de341dbae251aecc0a48ed437907c60da2 Mon Sep 17 00:00:00 2001 From: lby Date: Thu, 24 Oct 2024 14:23:45 +0800 Subject: [PATCH] fix(web): dashboard projects is loading more than expected (#1193) --- web/src/beta/features/AssetsManager/hooks.ts | 61 ++++---------- .../ContentsContainer/Projects/hooks.ts | 21 ++--- .../ContentsContainer/Projects/index.tsx | 12 +-- .../Visualizer/Crust/Infobox/index.tsx | 2 +- web/src/beta/hooks/useLoadMore.ts | 79 +++++++++++++++++++ web/src/beta/utils/infinite-scroll.ts | 26 ------ 6 files changed, 103 insertions(+), 98 deletions(-) create mode 100644 web/src/beta/hooks/useLoadMore.ts delete mode 100644 web/src/beta/utils/infinite-scroll.ts diff --git a/web/src/beta/features/AssetsManager/hooks.ts b/web/src/beta/features/AssetsManager/hooks.ts index d52d98446b..44a0657128 100644 --- a/web/src/beta/features/AssetsManager/hooks.ts +++ b/web/src/beta/features/AssetsManager/hooks.ts @@ -1,3 +1,4 @@ +import useLoadMore from "@reearth/beta/hooks/useLoadMore"; import { BreadcrumbItem } from "@reearth/beta/lib/reearth-ui"; import { ManagerLayout } from "@reearth/beta/ui/components/ManagerBase"; import { useAssetsFetcher } from "@reearth/services/api"; @@ -151,26 +152,11 @@ export default ({ isLoadingMoreRef.current = false; }, [hasMoreAssets, isRefetching, fetchMore, endCursor]); - const loadMoreRef = useRef<() => void>(loadMore); - loadMoreRef.current = loadMore; - - const assetsWrapperRef = useRef(null); - const assetsContentRef = useRef(null); - - const checkSize = useCallback(() => { - const parentElement = assetsWrapperRef.current; - const childElement = assetsContentRef.current; - - if (childElement && parentElement) { - if (childElement.offsetHeight - 14 < parentElement.offsetHeight) { - loadMoreRef.current?.(); - } - } - }, []); - - useEffect(() => { - checkSize(); - }, [assets, checkSize]); + const { wrapperRef: assetsWrapperRef, contentRef: assetsContentRef } = + useLoadMore({ + data: filteredAssets, + onLoadMore: loadMore + }); const [contentWidth, setContentWidth] = useState(0); @@ -182,9 +168,6 @@ export default ({ const checkSize = () => { if (childElement && parentElement) { - if (childElement.offsetHeight <= parentElement.offsetHeight) { - loadMoreRef.current?.(); - } setContentWidth(childElement.offsetWidth); } }; @@ -197,26 +180,11 @@ export default ({ checkSize(); - const handleScroll = () => { - if ( - childElement && - childElement.scrollHeight - - parentElement.scrollTop - - parentElement.clientHeight < - 50 - ) { - loadMoreRef.current?.(); - } - }; - - parentElement.addEventListener("scroll", handleScroll); - return () => { parentObserver.disconnect(); childObserver.disconnect(); - parentElement.removeEventListener("scroll", handleScroll); }; - }, [filteredAssets]); + }, [filteredAssets, assetsWrapperRef, assetsContentRef]); const handleAssetsCreate = useCallback( async (files?: FileList) => { @@ -247,12 +215,15 @@ export default ({ ? (localStorage.getItem(ASSETS_LAYOUT_STORAGE_KEY) as ManagerLayout) : "grid" ); - const handleLayoutChange = useCallback((newLayout?: ManagerLayout) => { - if (!newLayout) return; - localStorage.setItem(ASSETS_LAYOUT_STORAGE_KEY, newLayout); - setLayout(newLayout); - assetsWrapperRef.current?.scrollTo({ top: 0 }); - }, []); + const handleLayoutChange = useCallback( + (newLayout?: ManagerLayout) => { + if (!newLayout) return; + localStorage.setItem(ASSETS_LAYOUT_STORAGE_KEY, newLayout); + setLayout(newLayout); + assetsWrapperRef.current?.scrollTo({ top: 0 }); + }, + [assetsWrapperRef] + ); // path // TODO: support path with folder diff --git a/web/src/beta/features/Dashboard/ContentsContainer/Projects/hooks.ts b/web/src/beta/features/Dashboard/ContentsContainer/Projects/hooks.ts index e7fccc428e..98c135c2ef 100644 --- a/web/src/beta/features/Dashboard/ContentsContainer/Projects/hooks.ts +++ b/web/src/beta/features/Dashboard/ContentsContainer/Projects/hooks.ts @@ -1,9 +1,6 @@ import { useApolloClient } from "@apollo/client"; +import useLoadMore from "@reearth/beta/hooks/useLoadMore"; import { ManagerLayout } from "@reearth/beta/ui/components/ManagerBase"; -import { - autoFillPage, - onScrollToBottom -} from "@reearth/beta/utils/infinite-scroll"; import { useProjectFetcher } from "@reearth/services/api"; import { ProjectSortField, @@ -50,9 +47,6 @@ export default (workspaceId?: string) => { const [searchTerm, setSearchTerm] = useState(); const [sortValue, setSort] = useState("date-updated"); - const wrapperRef = useRef(null); - const contentRef = useRef(null); - const { starredProjects } = useStarredProjectsQuery(workspaceId); const { @@ -118,12 +112,10 @@ export default (workspaceId?: string) => { return loading || isRefetching; }, [isRefetching, loading]); - useEffect(() => { - if (wrapperRef.current && !isLoading && hasMoreProjects) { - handleGetMoreProjects(); - } - autoFillPage(wrapperRef, handleGetMoreProjects); - }, [handleGetMoreProjects, projects, hasMoreProjects, isLoading]); + const { wrapperRef, contentRef } = useLoadMore({ + data: filtedProjects, + onLoadMore: handleGetMoreProjects + }); const handleProjectSortChange = useCallback( (value?: string) => { @@ -241,7 +233,7 @@ export default (workspaceId?: string) => { parentObserver.disconnect(); childObserver.disconnect(); }; - }, []); + }, [wrapperRef, contentRef]); const handleImportProject = useCallback( async (event: React.ChangeEvent) => { @@ -300,7 +292,6 @@ export default (workspaceId?: string) => { handleProjectOpen, handleProjectCreate, handleProjectSelect, - handleScrollToBottom: onScrollToBottom, handleLayoutChange, handleProjectSortChange, handleSearch, diff --git a/web/src/beta/features/Dashboard/ContentsContainer/Projects/index.tsx b/web/src/beta/features/Dashboard/ContentsContainer/Projects/index.tsx index ad5bdda2f7..7a503a5b33 100644 --- a/web/src/beta/features/Dashboard/ContentsContainer/Projects/index.tsx +++ b/web/src/beta/features/Dashboard/ContentsContainer/Projects/index.tsx @@ -20,7 +20,6 @@ const Projects: FC<{ workspaceId?: string }> = ({ workspaceId }) => { const { filtedProjects, isLoading, - hasMoreProjects, selectedProject, projectCreatorVisible, wrapperRef, @@ -31,12 +30,10 @@ const Projects: FC<{ workspaceId?: string }> = ({ workspaceId }) => { sortValue, showProjectCreator, closeProjectCreator, - handleGetMoreProjects, handleProjectUpdate, handleProjectCreate, handleProjectOpen, handleProjectSelect, - handleScrollToBottom, handleLayoutChange, handleProjectSortChange, handleSearch, @@ -172,14 +169,7 @@ const Projects: FC<{ workspaceId?: string }> = ({ workspaceId }) => { )} - { - if (!isLoading && hasMoreProjects) { - handleScrollToBottom(e, handleGetMoreProjects); - } - }} - > + {filtedProjects.map((project) => diff --git a/web/src/beta/features/Visualizer/Crust/Infobox/index.tsx b/web/src/beta/features/Visualizer/Crust/Infobox/index.tsx index 0b48adaaf7..0866d82749 100644 --- a/web/src/beta/features/Visualizer/Crust/Infobox/index.tsx +++ b/web/src/beta/features/Visualizer/Crust/Infobox/index.tsx @@ -186,7 +186,7 @@ const Infobox: FC = ({ handleClassName={INFOBOX_DRAG_HANDLE_CLASS_NAME} onMoveEnd={handleMoveEnd} dragDisabled={false} - gap={GAP_DEFAULT_VALUE} + gap={gapField?.value ?? GAP_DEFAULT_VALUE} /> )} diff --git a/web/src/beta/hooks/useLoadMore.ts b/web/src/beta/hooks/useLoadMore.ts new file mode 100644 index 0000000000..1d5f7e4973 --- /dev/null +++ b/web/src/beta/hooks/useLoadMore.ts @@ -0,0 +1,79 @@ +import { useCallback, useEffect, useRef } from "react"; + +type Props = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any; + onLoadMore: () => void; +}; + +const BOTTOM_ALLOWANCE = 50; // when there are 50px left to scroll, load more +const SHORTER_LIMIT = 20; // when the content minuse this is smaller than wrapper we load more + +export default ({ data, onLoadMore }: Props) => { + const wrapperRef = useRef(null); + const contentRef = useRef(null); + + const onLoadMoreRef = useRef(onLoadMore); + onLoadMoreRef.current = onLoadMore; + + const checkForLoadMore = useCallback(() => { + const wrapperElement = wrapperRef.current; + const contentElement = contentRef.current; + + if (wrapperElement && contentElement) { + if ( + contentElement.offsetHeight - SHORTER_LIMIT < + wrapperElement.offsetHeight || + contentElement.scrollHeight - + wrapperElement.scrollTop - + wrapperElement.clientHeight < + BOTTOM_ALLOWANCE + ) { + onLoadMoreRef.current?.(); + } + } + }, []); + + // Check on data change + useEffect(() => { + checkForLoadMore(); + }, [data, checkForLoadMore]); + + // Check on scroll + useEffect(() => { + const wrapperElement = wrapperRef.current; + if (!wrapperElement) return; + + wrapperElement.addEventListener("scroll", checkForLoadMore); + + return () => { + wrapperElement.removeEventListener("scroll", checkForLoadMore); + }; + }, [data, checkForLoadMore]); + + // Check on resize + useEffect(() => { + const wrapperElement = wrapperRef.current; + const contentElement = contentRef.current; + + if (!wrapperElement || !contentElement) return; + + const wrapperObserver = new ResizeObserver(checkForLoadMore); + const contentObserver = new ResizeObserver(checkForLoadMore); + + wrapperObserver.observe(wrapperElement); + contentObserver.observe(contentElement); + + checkForLoadMore(); + + return () => { + wrapperObserver.disconnect(); + contentObserver.disconnect(); + }; + }, [data, checkForLoadMore]); + + return { + wrapperRef, + contentRef + }; +}; diff --git a/web/src/beta/utils/infinite-scroll.ts b/web/src/beta/utils/infinite-scroll.ts deleted file mode 100644 index 44227be650..0000000000 --- a/web/src/beta/utils/infinite-scroll.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { MutableRefObject } from "react"; - -export function autoFillPage( - ref: MutableRefObject, - onLoadMore?: () => void -) { - if ( - ref.current && - ref.current?.scrollHeight <= document.documentElement.clientHeight - ) { - onLoadMore?.(); - } -} - -export function onScrollToBottom( - { currentTarget }: React.UIEvent, - onLoadMore?: () => void, - threshold = 5 -) { - if ( - currentTarget.scrollTop + currentTarget.clientHeight >= - currentTarget.scrollHeight - threshold - ) { - onLoadMore?.(); - } -}