Просмотр исходного кода

Merge branch 'feat/gw7757-implement-useSWRInifinitexRecentlyUpdated' of https://github.com/weseek/growi into feat/gw7758-reimplementation-api-v3-pages-recent

I Komang Mudana 4 лет назад
Родитель
Сommit
df86d70330

+ 59 - 0
packages/app/src/components/Sidebar/InfiniteScroll.tsx

@@ -0,0 +1,59 @@
+import React, { Ref, useEffect, useState } from 'react';
+import type { SWRInfiniteResponse } from 'swr/infinite';
+
+type Props<T> = {
+  swr: SWRInfiniteResponse<T>
+  children:any,
+  loadingIndicator?: React.ReactNode
+  endingIndicator?: React.ReactNode
+  isReachingEnd?: boolean,
+  offset?: number
+}
+
+const useIntersection = <T extends HTMLElement>(): [boolean, Ref<T>] => {
+  const [intersecting, setIntersecting] = useState<boolean>(false);
+  const [element, setElement] = useState<HTMLElement>();
+  useEffect(() => {
+    if (!element) return;
+    const observer = new IntersectionObserver((entries) => {
+      setIntersecting(entries[0]?.isIntersecting);
+    });
+    observer.observe(element);
+    return () => observer.unobserve(element);
+  }, [element]);
+  return [intersecting, el => el && setElement(el)];
+};
+
+const InfiniteScroll = <T, >(props: Props<T>): React.ReactElement<Props<T>> => {
+  const {
+    swr,
+    swr: { setSize, data, isValidating },
+    children,
+    loadingIndicator,
+    endingIndicator,
+    isReachingEnd,
+    offset = 0,
+  } = props;
+
+  const [intersecting, ref] = useIntersection<HTMLDivElement>();
+
+  // const ending = typeof isReachingEnd === 'function' ? isReachingEnd(swr) : isReachingEnd;
+
+  useEffect(() => {
+    if (intersecting && !isValidating && !isReachingEnd) {
+      setSize(size => size + 1);
+    }
+  }, [intersecting, isValidating, setSize, isReachingEnd]);
+
+  return (
+    <>
+      {typeof children === 'function' ? data?.map(item => children(item)) : children}
+      <div style={{ position: 'relative' }}>
+        <div ref={ref} style={{ position: 'absolute', top: offset }}></div>
+        {isReachingEnd ? endingIndicator : loadingIndicator}
+      </div>
+    </>
+  );
+};
+
+export default InfiniteScroll;

+ 26 - 8
packages/app/src/components/Sidebar/RecentChanges.tsx

@@ -10,10 +10,11 @@ import { UserPicture, FootstampIcon } from '@growi/ui';
 import { DevidedPagePath } from '@growi/core';
 import { DevidedPagePath } from '@growi/core';
 
 
 import PagePathHierarchicalLink from '~/components/PagePathHierarchicalLink';
 import PagePathHierarchicalLink from '~/components/PagePathHierarchicalLink';
-import { useSWRxRecentlyUpdated } from '~/stores/page';
+import { useSWRxRecentlyUpdated, useSWRInifinitexRecentlyUpdated } from '~/stores/page';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import LinkedPagePath from '~/models/linked-page-path';
 import LinkedPagePath from '~/models/linked-page-path';
+import InfiniteScroll from './InfiniteScroll';
 
 
 
 
 import FormattedDistanceDate from '../FormattedDistanceDate';
 import FormattedDistanceDate from '../FormattedDistanceDate';
@@ -120,13 +121,21 @@ SmallPageItem.propTypes = {
   page: PropTypes.any,
   page: PropTypes.any,
 };
 };
 
 
+function LoadingIndicator() {
+  return (
+    <div className="text-muted text-center">
+      <i className="fa fa-2x fa-spinner fa-pulse mr-1"></i>
+    </div>
+  );
+}
 
 
 const RecentChanges = (): JSX.Element => {
 const RecentChanges = (): JSX.Element => {
-
+  const PER_PAGE = 20;
   const { t } = useTranslation();
   const { t } = useTranslation();
-  const { data: pages, mutate } = useSWRxRecentlyUpdated();
-
+  const swr = useSWRInifinitexRecentlyUpdated();
   const [isRecentChangesSidebarSmall, setIsRecentChangesSidebarSmall] = useState(false);
   const [isRecentChangesSidebarSmall, setIsRecentChangesSidebarSmall] = useState(false);
+  const isReachingEnd = swr.data && swr.data[swr.data.length - 1]?.length < PER_PAGE;
+  const pages = swr.data?.flat()?.filter((item, index) => index === swr.data?.flat().findIndex(page => page._id === item._id));
 
 
   const retrieveSizePreferenceFromLocalStorage = useCallback(() => {
   const retrieveSizePreferenceFromLocalStorage = useCallback(() => {
     if (window.localStorage.isRecentChangesSidebarSmall === 'true') {
     if (window.localStorage.isRecentChangesSidebarSmall === 'true') {
@@ -148,7 +157,7 @@ const RecentChanges = (): JSX.Element => {
     <>
     <>
       <div className="grw-sidebar-content-header p-3 d-flex">
       <div className="grw-sidebar-content-header p-3 d-flex">
         <h3 className="mb-0  text-nowrap">{t('Recent Changes')}</h3>
         <h3 className="mb-0  text-nowrap">{t('Recent Changes')}</h3>
-        <button type="button" className="btn btn-sm ml-auto grw-btn-reload" onClick={() => mutate()}>
+        <button type="button" className="btn btn-sm ml-auto grw-btn-reload" onClick={() => swr.mutate()}>
           <i className="icon icon-reload"></i>
           <i className="icon icon-reload"></i>
         </button>
         </button>
         <div className="d-flex align-items-center">
         <div className="d-flex align-items-center">
@@ -167,9 +176,18 @@ const RecentChanges = (): JSX.Element => {
       </div>
       </div>
       <div className="grw-recent-changes p-3">
       <div className="grw-recent-changes p-3">
         <ul className="list-group list-group-flush">
         <ul className="list-group list-group-flush">
-          {(pages || []).map(page => (isRecentChangesSidebarSmall
-            ? <SmallPageItem key={page._id} page={page} />
-            : <LargePageItem key={page._id} page={page} />))}
+          <InfiniteScroll
+            swr={swr}
+            loadingIndicator={<LoadingIndicator />}
+            isReachingEnd={isReachingEnd}
+          >
+            {pages?.map(page => (
+              isRecentChangesSidebarSmall
+                ? <SmallPageItem key={page._id} page={page} />
+                : <LargePageItem key={page._id} page={page} />
+            ))
+            }
+          </InfiniteScroll>
         </ul>
         </ul>
       </div>
       </div>
     </>
     </>

+ 15 - 1
packages/app/src/stores/page.tsx

@@ -1,4 +1,5 @@
 import useSWR, { SWRResponse } from 'swr';
 import useSWR, { SWRResponse } from 'swr';
+import useSWRInfinite, { SWRInfiniteResponse } from 'swr/infinite';
 import useSWRImmutable from 'swr/immutable';
 import useSWRImmutable from 'swr/immutable';
 
 
 import { apiv3Get } from '~/client/util/apiv3-client';
 import { apiv3Get } from '~/client/util/apiv3-client';
@@ -33,7 +34,20 @@ export const useSWRxRecentlyUpdated = (): SWRResponse<(IPageHasId)[], Error> =>
     endpoint => apiv3Get<{ pages:(IPageHasId)[] }>(endpoint).then(response => response.data?.pages),
     endpoint => apiv3Get<{ pages:(IPageHasId)[] }>(endpoint).then(response => response.data?.pages),
   );
   );
 };
 };
-
+export const useSWRInifinitexRecentlyUpdated = () : SWRInfiniteResponse<(IPageHasId)[], Error> => {
+  const getKey = (offset: number, previousData: any) => {
+    if (previousData && !previousData.length) return null;
+    return `/pages/recent?offset=${offset}`;
+  };
+  return useSWRInfinite(
+    getKey,
+    (endpoint: string) => apiv3Get<{ pages:(IPageHasId)[] }>(endpoint).then(response => response.data?.pages),
+    {
+      initialSize: 1,
+      revalidateFirstPage: false,
+    },
+  );
+};
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
 export const useSWRxPageList = (path: string | null, pageNumber?: number, termNumber?: number): SWRResponse<IPagingResult<IPageHasId>, Error> => {
 export const useSWRxPageList = (path: string | null, pageNumber?: number, termNumber?: number): SWRResponse<IPagingResult<IPageHasId>, Error> => {