Yuki Takei 4 лет назад
Родитель
Сommit
5471f053fa
1 измененных файлов с 83 добавлено и 55 удалено
  1. 83 55
      packages/app/src/components/Sidebar/PageTree/ItemsTree.tsx

+ 83 - 55
packages/app/src/components/Sidebar/PageTree/ItemsTree.tsx

@@ -1,6 +1,12 @@
-import React, { FC, useEffect, useState } from 'react';
+import React, {
+  FC, useEffect, useRef, useState, useMemo,
+} from 'react';
 import { useTranslation } from 'react-i18next';
 
+import { debounce } from 'throttle-debounce';
+
+import loggerFactory from '~/utils/logger';
+
 import { usePageTreeTermManager, useSWRxPageAncestorsChildren, useSWRxRootPage } from '~/stores/page-listing';
 import { TargetAndAncestors } from '~/interfaces/page-listing-results';
 import { IPageHasId, IPageToDeleteWithMeta } from '~/interfaces/page';
@@ -23,6 +29,8 @@ import Item from './Item';
 
 const SCROLL_OFFSET_TOP = 60; // approximate height of navigation
 
+const logger = loggerFactory('growi:cli:ItemsTree');
+
 /*
  * Utility to generate initial node
  */
@@ -62,39 +70,6 @@ const generateInitialNodeAfterResponse = (ancestorsChildren: Record<string, Part
   return rootNode;
 };
 
-type ItemsTreeProps = {
-  isEnableActions: boolean
-  targetPath: string
-  targetPathOrId?: string
-  targetAndAncestorsData?: TargetAndAncestors
-}
-
-const renderByInitialNode = (
-    initialNode: ItemNode,
-    isEnableActions: boolean,
-    targetPathOrId?: string,
-    isEnabledAttachTitleHeader?: boolean,
-    onRenamed?: () => void,
-    onClickDuplicateMenuItem?: (pageToDuplicate: IPageForPageDuplicateModal) => void,
-    onClickDeleteMenuItem?: (pageToDelete: IPageToDeleteWithMeta) => void,
-): JSX.Element => {
-
-  return (
-    <ul className="grw-pagetree list-group p-3">
-      <Item
-        key={initialNode.page.path}
-        targetPathOrId={targetPathOrId}
-        itemNode={initialNode}
-        isOpen
-        isEnabledAttachTitleHeader={isEnabledAttachTitleHeader}
-        isEnableActions={isEnableActions}
-        onRenamed={onRenamed}
-        onClickDuplicateMenuItem={onClickDuplicateMenuItem}
-        onClickDeleteMenuItem={onClickDeleteMenuItem}
-      />
-    </ul>
-  );
-};
 
 // Auto scroll by jquery slimScroll
 const scrollPageTree = () => {
@@ -107,6 +82,13 @@ const scrollPageTree = () => {
 };
 
 
+type ItemsTreeProps = {
+  isEnableActions: boolean
+  targetPath: string
+  targetPathOrId?: string
+  targetAndAncestorsData?: TargetAndAncestors
+}
+
 /*
  * ItemsTree
  */
@@ -132,8 +114,13 @@ const ItemsTree: FC<ItemsTreeProps> = (props: ItemsTreeProps) => {
   const { advance: advanceFts } = useFullTextSearchTermManager();
   const { advance: advanceDpl } = useDescendantsPageListForCurrentPathTermManager();
 
-  const [isScrolled, setIsScrolled] = useState(false);
-  const [isRenderedCompletely, setIsRenderedCompletely] = useState(false);
+  const [isInitialScrollCompleted, setIsInitialScrollCompleted] = useState(false);
+
+  const rootElemRef = useRef(null);
+
+
+  const isSecondStageRendering = ancestorsChildrenData != null && rootPageData != null;
+
 
   useEffect(() => {
     if (socket == null) {
@@ -157,15 +144,47 @@ const ItemsTree: FC<ItemsTreeProps> = (props: ItemsTreeProps) => {
     advanceDpl();
   };
 
-  useEffect(() => {
-    if (isRenderedCompletely && !isScrolled) {
+  const initialScrollDebounced = useMemo(() => {
+    return debounce(100, () => {
+      logger.debug('initialScrollDebounced called');
+
+      if (isInitialScrollCompleted) {
+        return;
+      }
+
+      logger.debug('scrollPageTree has invoked after debounce');
+
       document.dispatchEvent(new CustomEvent(SidebarScrollerEvent.RESET_SCROLLBAR));
       // use setTimeout as resetScrollbar in StickyStretchableScroller component uses debounce and wait 100ms
       setTimeout(scrollPageTree, 100);
 
-      setIsScrolled(true);
+      setIsInitialScrollCompleted(true);
+    });
+  }, [isInitialScrollCompleted]);
+
+
+  // ***************************  Auto Scroll  ***************************
+  useEffect(() => {
+    if (!isSecondStageRendering || isInitialScrollCompleted) {
+      return;
+    }
+
+    const rootElement = rootElemRef.current as HTMLElement | null;
+    if (rootElement == null) {
+      return;
     }
-  }, [isRenderedCompletely, isScrolled]);
+
+    const observerCallback = (mutationRecords: MutationRecord[]) => {
+      mutationRecords.forEach(() => initialScrollDebounced());
+    };
+
+    const observer = new MutationObserver(observerCallback);
+    observer.observe(rootElement, { childList: true, subtree: true });
+    return () => {
+      observer.disconnect();
+    };
+  }, [initialScrollDebounced, isInitialScrollCompleted, isSecondStageRendering]);
+  // *******************************  end  *******************************
 
   const onClickDuplicateMenuItem = (pageToDuplicate: IPageForPageDuplicateModal) => {
     // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -209,30 +228,39 @@ const ItemsTree: FC<ItemsTreeProps> = (props: ItemsTreeProps) => {
     return null;
   }
 
+  let initialItemNode;
   /*
-   * Render completely
+   * Render second stage
    */
-  if (ancestorsChildrenData != null && rootPageData != null && !isRenderedCompletely) {
-    const initialNode = generateInitialNodeAfterResponse(ancestorsChildrenData.ancestorsChildren, new ItemNode(rootPageData.rootPage));
-    setIsRenderedCompletely(true);
-    return renderByInitialNode(
-      // eslint-disable-next-line max-len
-      initialNode, isEnableActions, targetPathOrId, isEnabledAttachTitleHeader, onRenamed, onClickDuplicateMenuItem, onClickDeleteMenuItem,
-    );
+  if (isSecondStageRendering) {
+    initialItemNode = generateInitialNodeAfterResponse(ancestorsChildrenData.ancestorsChildren, new ItemNode(rootPageData.rootPage));
   }
-
   /*
    * Before swr response comes back
    */
-  if (targetAndAncestorsData != null) {
-    const initialNode = generateInitialNodeBeforeResponse(targetAndAncestorsData.targetAndAncestors);
-    return renderByInitialNode(
-      // eslint-disable-next-line max-len
-      initialNode, isEnableActions, targetPathOrId, isEnabledAttachTitleHeader, onRenamed, onClickDuplicateMenuItem, onClickDeleteMenuItem,
+  else if (targetAndAncestorsData != null) {
+    initialItemNode = generateInitialNodeBeforeResponse(targetAndAncestorsData.targetAndAncestors);
+  }
+
+  if (initialItemNode != null) {
+    return (
+      <ul className="grw-pagetree list-group p-3" ref={rootElemRef}>
+        <Item
+          key={initialItemNode.page.path}
+          targetPathOrId={targetPathOrId}
+          itemNode={initialItemNode}
+          isOpen
+          isEnabledAttachTitleHeader={isEnabledAttachTitleHeader}
+          isEnableActions={isEnableActions}
+          onRenamed={onRenamed}
+          onClickDuplicateMenuItem={onClickDuplicateMenuItem}
+          onClickDeleteMenuItem={onClickDeleteMenuItem}
+        />
+      </ul>
     );
   }
 
-  return null;
+  return <></>;
 };
 
 export default ItemsTree;