Load pages and ensure scrollbar is always visible (#760)

This commit is contained in:
Montassar Ghanmy
2024-12-09 16:27:09 +01:00
committed by GitHub
parent 4c05cf9dee
commit 9bc8b29a9d
2 changed files with 51 additions and 18 deletions
@@ -20,7 +20,7 @@ export const useDriveItem = (id: string) => {
// const children = useRecoilValue(DriveItemChildrenAtom(id)); // const children = useRecoilValue(DriveItemChildrenAtom(id));
const [children, setChildren] = useRecoilState(DriveItemChildrenAtom(id)); const [children, setChildren] = useRecoilState(DriveItemChildrenAtom(id));
const [loading, setLoading] = useRecoilState(LoadingStateInitTrue('useDriveItem-' + id)); const [loading, setLoading] = useRecoilState(LoadingStateInitTrue('useDriveItem-' + id));
const [, setPaginateItem] = useRecoilState(DriveItemPagination); const [paginateItem, setPaginateItem] = useRecoilState(DriveItemPagination);
const { const {
refresh: refreshItem, refresh: refreshItem,
create, create,
@@ -172,6 +172,7 @@ export const useDriveItem = (id: string) => {
remove, remove,
refresh, refresh,
restore, restore,
paginateItem,
loadNextPage, loadNextPage,
}; };
}; };
@@ -10,7 +10,7 @@ import { useDriveUpload } from '@features/drive/hooks/use-drive-upload';
import { DriveItemSelectedList, DriveItemSort } from '@features/drive/state/store'; import { DriveItemSelectedList, DriveItemSort } from '@features/drive/state/store';
import { formatBytes } from '@features/drive/utils'; import { formatBytes } from '@features/drive/utils';
import useRouterCompany from '@features/router/hooks/use-router-company'; import useRouterCompany from '@features/router/hooks/use-router-company';
import _ from 'lodash'; import _, { get, set } from 'lodash';
import { memo, Suspense, useCallback, useEffect, useRef, useState } from 'react'; import { memo, Suspense, useCallback, useEffect, useRef, useState } from 'react';
import { atomFamily, useRecoilState, useSetRecoilState } from 'recoil'; import { atomFamily, useRecoilState, useSetRecoilState } from 'recoil';
import { DrivePreview } from '../../viewer/drive-preview'; import { DrivePreview } from '../../viewer/drive-preview';
@@ -77,9 +77,9 @@ export default memo(
? (user?.companies || []).find(company => company?.company.id === companyId)?.role ? (user?.companies || []).find(company => company?.company.id === companyId)?.role
: 'member'; : 'member';
setTdriveTabToken(tdriveTabContextToken || null); setTdriveTabToken(tdriveTabContextToken || null);
const [ filter ] = useRecoilState(SharedWithMeFilterState); const [filter] = useRecoilState(SharedWithMeFilterState);
const { viewId, dirId, itemId } = useRouteState(); const { viewId, dirId, itemId } = useRouteState();
const [sortLabel] = useRecoilState(DriveItemSort) const [sortLabel] = useRecoilState(DriveItemSort);
const [parentId, _setParentId] = useRecoilState( const [parentId, _setParentId] = useRecoilState(
DriveCurrentFolderAtom({ DriveCurrentFolderAtom({
context: context, context: context,
@@ -94,8 +94,6 @@ export default memo(
history.push(RouterServices.generateRouteFromState({ viewId: parentId })); history.push(RouterServices.generateRouteFromState({ viewId: parentId }));
}, [viewId, dirId]); }, [viewId, dirId]);
const [loadingParentChange, setLoadingParentChange] = useState(false); const [loadingParentChange, setLoadingParentChange] = useState(false);
const { const {
sharedWithMe, sharedWithMe,
@@ -108,6 +106,7 @@ export default memo(
loading: loadingParent, loading: loadingParent,
path, path,
loadNextPage, loadNextPage,
paginateItem,
} = useDriveItem(parentId); } = useDriveItem(parentId);
const { uploadTree } = useDriveUpload(); const { uploadTree } = useDriveUpload();
@@ -145,12 +144,13 @@ export default memo(
if (item?.id) setUploadModalState({ open: true, parent_id: item.id }); if (item?.id) setUploadModalState({ open: true, parent_id: item.id });
}, [item?.id, setUploadModalState]); }, [item?.id, setUploadModalState]);
const items = item?.is_directory === false const items =
item?.is_directory === false
? //We use this hack for public shared single file ? //We use this hack for public shared single file
item item
? [item] ? [item]
: [] : []
: children : children;
const documents = items.filter(i => !i.is_directory); const documents = items.filter(i => !i.is_directory);
@@ -241,32 +241,33 @@ export default memo(
} }
// Infinite scroll // Infinite scroll
const scrollViwer = useRef<HTMLDivElement>(null); const scrollViewer = useRef<HTMLDivElement>(null);
const handleScroll = async () => { const handleScroll = async () => {
const scrollTop = scrollViwer.current?.scrollTop || 0; const scrollTop = scrollViewer.current?.scrollTop || 0;
const scrollHeight = scrollViwer.current?.scrollHeight || 0; const scrollHeight = scrollViewer.current?.scrollHeight || 0;
const clientHeight = scrollViwer.current?.clientHeight || 0; const clientHeight = scrollViewer.current?.clientHeight || 0;
if (scrollTop > 0 && scrollTop + clientHeight >= scrollHeight) { if (scrollTop > 0 && scrollTop + clientHeight >= scrollHeight) {
await loadNextPage(parentId); await loadNextPage(parentId);
} }
}; };
useEffect(() => { useEffect(() => {
if(!loading) scrollViwer.current?.addEventListener('scroll', handleScroll, { passive: true }); if (!loading)
scrollViewer.current?.addEventListener('scroll', handleScroll, { passive: true });
return () => { return () => {
scrollViwer.current?.removeEventListener('scroll', handleScroll); scrollViewer.current?.removeEventListener('scroll', handleScroll);
}; };
}, [parentId, loading]); }, [parentId, loading]);
// Scroll to item in view // Scroll to item in view
const scrollTillItemInView = itemId && itemId?.length > 0; const scrollTillItemInView = itemId && itemId?.length > 0;
const scrollItemId = itemId || ''; const scrollItemId = itemId || '';
useEffect(() => { useEffect(() => {
const itemInChildren = children.find(item => item.id === scrollItemId); const itemInChildren = children.find(item => item.id === scrollItemId);
if (!loading && scrollTillItemInView && !itemInChildren) { if (!loading && scrollTillItemInView && !itemInChildren) {
scrollViwer.current?.scrollTo(0, scrollViwer.current?.scrollHeight); scrollViewer.current?.scrollTo(0, scrollViewer.current?.scrollHeight);
} else { } else {
if (!loading && itemInChildren) { if (!loading && itemInChildren) {
// scroll to preview item using id for current preview routes // scroll to preview item using id for current preview routes
@@ -279,6 +280,35 @@ export default memo(
} }
}, [loading, children]); }, [loading, children]);
// Determine the number of items that can fit within the scroll viewer's visible area before the scrollbar appears.
const getItemsPerPage = () => {
const scrollViewerElement = scrollViewer?.current || null;
const itemHeight = scrollViewerElement?.firstElementChild?.clientHeight || 0;
const viewerHeight = scrollViewerElement?.clientHeight || 0;
return itemHeight > 0 ? Math.ceil(viewerHeight / itemHeight) : 0;
};
const [itemsPerPage, setItemsPerPage] = useState(0);
useEffect(() => {
const handleResize = () => {
setItemsPerPage(getItemsPerPage());
};
handleResize(); // intially set the items per page for the current view
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [getItemsPerPage]);
// Load additional pages as needed to ensure the scrollbar remains visible
useEffect(() => {
const currentPage = Math.floor((paginateItem?.page || 1) / (paginateItem?.limit || 1));
const targetPages = Math.ceil(itemsPerPage / (paginateItem?.limit || 1));
if (!loading && currentPage < targetPages) {
loadNextPage(parentId);
}
}, [paginateItem, loading, parentId, itemsPerPage]);
return ( return (
<> <>
{viewId == 'shared-with-me' ? ( {viewId == 'shared-with-me' ? (
@@ -418,7 +448,9 @@ export default memo(
sortLabel.order === 'asc' ? 'transform rotate-180' : '' sortLabel.order === 'asc' ? 'transform rotate-180' : ''
}`} }`}
/> />
<span>{Languages.t('components.item_context_menu.sorting.selected.' + sortLabel.by)}</span> <span>
{Languages.t('components.item_context_menu.sorting.selected.' + sortLabel.by)}
</span>
<ChevronDownIcon className="h-4 w-4 ml-2 -mr-1" /> <ChevronDownIcon className="h-4 w-4 ml-2 -mr-1" />
</Button> </Button>
</Menu> </Menu>
@@ -439,7 +471,7 @@ export default memo(
</div> </div>
<DndContext sensors={sensors} onDragEnd={handleDragEnd} onDragStart={handleDragStart}> <DndContext sensors={sensors} onDragEnd={handleDragEnd} onDragStart={handleDragStart}>
<div className="grow overflow-auto" ref={scrollViwer}> <div className="grow overflow-auto" ref={scrollViewer}>
{items.length === 0 && !loading && ( {items.length === 0 && !loading && (
<div className="mt-4 text-center border-2 border-dashed rounded-md p-8"> <div className="mt-4 text-center border-2 border-dashed rounded-md p-8">
<Subtitle className="block mb-2"> <Subtitle className="block mb-2">