Yuki Takei 4 лет назад
Родитель
Сommit
475ee4981a

+ 4 - 0
packages/app/src/client/interfaces/selectable-all.ts

@@ -3,6 +3,10 @@ export interface ISelectable {
   deselect: () => void,
 }
 
+export interface ISelectableAndIndeterminatable extends ISelectable {
+  setIndeterminate: () => void,
+}
+
 export interface ISelectableAll {
   selectAll: () => void,
   deselectAll: () => void,

+ 5 - 4
packages/app/src/components/PageList/PageListItemL.tsx

@@ -26,7 +26,7 @@ type Props = {
   isSelected?: boolean, // is item selected(focused)
   isEnableActions?: boolean,
   showPageUpdatedTime?: boolean, // whether to show page's updated time at the top-right corner of item
-  onClickCheckbox?: (pageId: string) => void,
+  onCheckboxChanged?: (isChecked: boolean, pageId: string) => void,
   onClickItem?: (pageId: string) => void,
   onClickDeleteButton?: (pageId: string) => void,
 }
@@ -34,8 +34,9 @@ type Props = {
 const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (props: Props, ref): JSX.Element => {
   const {
     // todo: refactoring variable name to clear what changed
-    page: { pageData, pageMeta }, isSelected, onClickItem, onClickCheckbox, isEnableActions,
+    page: { pageData, pageMeta }, isSelected, isEnableActions,
     showPageUpdatedTime,
+    onClickItem, onCheckboxChanged,
   } = props;
 
   const inputRef = useRef<HTMLInputElement>(null);
@@ -94,13 +95,13 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
       >
         <div className="d-flex">
           {/* checkbox */}
-          {onClickCheckbox != null && (
+          {onCheckboxChanged != null && (
             <div className="d-flex align-items-center justify-content-center pl-md-2 pl-3">
               <CustomInput
                 type="checkbox"
                 id={`cbDelete-${pageData._id}`}
                 innerRef={inputRef}
-                onChange={() => { onClickCheckbox(pageData._id) }}
+                onChange={(e) => { onCheckboxChanged(e.target.checked, pageData._id) }}
               />
             </div>
           )}

+ 10 - 22
packages/app/src/components/SearchPage.tsx

@@ -6,16 +6,15 @@ import { useTranslation } from 'react-i18next';
 import { parse as parseQuerystring } from 'querystring';
 
 import AppContainer from '~/client/services/AppContainer';
-import { IFormattedSearchResult, IPageSearchMeta } from '~/interfaces/search';
-import { IPageWithMeta } from '~/interfaces/page';
-
+import { IFormattedSearchResult } from '~/interfaces/search';
+import { ISelectableAll, ISelectableAndIndeterminatable } from '~/client/interfaces/selectable-all';
 import { ISearchConditions, ISearchConfigurations, useSWRxFullTextSearch } from '~/stores/search';
+
 import PaginationWrapper from './PaginationWrapper';
-import { OperateAllControl, useSelectAll } from './SearchPage/OperateAllControl';
+import { OperateAllControl } from './SearchPage/OperateAllControl';
 import SearchControl from './SearchPage/SearchControl';
 
 import { SearchPageBase } from './SearchPage2/SearchPageBase';
-import { ISelectableAll } from '~/client/interfaces/selectable-all';
 
 
 // TODO: replace with "customize:showPageLimitationS"
@@ -105,6 +104,7 @@ export const SearchPage = (props: Props): JSX.Element => {
     limit: INITIAL_PAGIONG_SIZE,
   });
 
+  const selectAllControlRef = useRef<ISelectableAndIndeterminatable|null>(null);
   const searchPageBaseRef = useRef<ISelectableAll|null>(null);
 
   const { data, conditions } = useSWRxFullTextSearch(keyword, {
@@ -113,11 +113,6 @@ export const SearchPage = (props: Props): JSX.Element => {
     ...configurationsByPagination,
   });
 
-  const {
-    checkboxType: selectAllPagesCheckboxType,
-    setSelectedCount: setSelectedPagesCount,
-  } = useSelectAll(data?.meta.hitsCount);
-
   const searchInvokedHandler = useCallback((_keyword: string, newConfigurations: Partial<ISearchConfigurations>) => {
     setKeyword(_keyword);
     setConfigurationsByControl(newConfigurations);
@@ -133,7 +128,7 @@ export const SearchPage = (props: Props): JSX.Element => {
     if (isChecked) {
       instance.selectAll();
     }
-    if (!isChecked) {
+    else {
       instance.deselectAll();
     }
   }, []);
@@ -159,8 +154,8 @@ export const SearchPage = (props: Props): JSX.Element => {
     newUrl.searchParams.append('q', keyword);
     window.history.pushState('', `Search - ${keyword}`, `${newUrl.pathname}${newUrl.search}`);
   }, [keyword]);
-
   const hitsCount = data?.meta.hitsCount;
+
   const { offset, limit } = conditions;
 
   const deleteAllControl = useMemo(() => {
@@ -168,7 +163,7 @@ export const SearchPage = (props: Props): JSX.Element => {
 
     return (
       <OperateAllControl
-        checkboxType={selectAllPagesCheckboxType}
+        ref={selectAllControlRef}
         isCheckboxDisabled={isDisabled}
         onCheckboxChanged={selectAllCheckboxChangedHandler}
       >
@@ -183,7 +178,7 @@ export const SearchPage = (props: Props): JSX.Element => {
         </button>
       </OperateAllControl>
     );
-  }, [hitsCount, selectAllCheckboxChangedHandler, selectAllPagesCheckboxType, t]);
+  }, [hitsCount, selectAllCheckboxChangedHandler, t]);
 
   const searchControl = useMemo(() => {
     return (
@@ -230,19 +225,12 @@ export const SearchPage = (props: Props): JSX.Element => {
     );
   }, [conditions, configurationsByPagination?.limit, data, pagingNumberChangedHandler]);
 
-  // reset selected count when data is refetched
-  useEffect(() => {
-    if (data != null) {
-      setSelectedPagesCount(0);
-    }
-  }, [data, setSelectedPagesCount]);
-
   return (
     <SearchPageBase
       ref={searchPageBaseRef}
       appContainer={appContainer}
       pages={data?.data}
-      onSelectedPagesByCheckboxesChanged={setSelectedPagesCount}
+      onSelectedPagesByCheckboxesChanged={selectedPagesByCheckboxesChangedHandler}
       // Components
       searchControl={searchControl}
       searchResultListHead={searchResultListHead}

+ 32 - 55
packages/app/src/components/SearchPage/OperateAllControl.tsx

@@ -1,82 +1,57 @@
 import React, {
-  ChangeEvent, FC, useEffect, useMemo, useRef, useState,
+  ChangeEvent, forwardRef, ForwardRefRenderFunction, useImperativeHandle, useRef,
 } from 'react';
 import { CustomInput } from 'reactstrap';
+import { ISelectableAndIndeterminatable } from '~/client/interfaces/selectable-all';
 import { IndeterminateInputElement } from '~/interfaces/indeterminate-input-elm';
-import { CheckboxType } from '~/interfaces/search';
-
-
-export type SelectAllHook = {
-  checkboxType: CheckboxType,
-  setSelectedCount: (selectedCount: number) => void,
-}
-
-export const useSelectAll = (totalItemsCount: number | undefined): SelectAllHook => {
-  const [selectedCount, setSelectedCount] = useState(0);
-
-  const checkboxType = useMemo(() => {
-    if (selectedCount === 0) {
-      return CheckboxType.NONE_CHECKED;
-    }
-    if (selectedCount === totalItemsCount) {
-      return CheckboxType.ALL_CHECKED;
-    }
-    return CheckboxType.INDETERMINATE;
-  }, [selectedCount, totalItemsCount]);
-
-  return {
-    checkboxType,
-    setSelectedCount,
-  };
-};
-
 
 type Props = {
-  checkboxType: CheckboxType,
   isCheckboxDisabled?: boolean,
   onCheckboxChanged?: (isChecked: boolean) => void,
 
   children?: React.ReactNode,
 }
 
-export const OperateAllControl :FC<Props> = React.memo((props: Props) => {
+const OperateAllControlSubstance: ForwardRefRenderFunction<ISelectableAndIndeterminatable, Props> = (props: Props, ref): JSX.Element => {
   const {
-    checkboxType,
     isCheckboxDisabled,
     onCheckboxChanged,
 
     children,
   } = props;
 
+  const selectAllCheckboxElm = useRef<IndeterminateInputElement>(null);
+
+  // publish ISelectable methods
+  useImperativeHandle(ref, () => ({
+    select: () => {
+      const input = selectAllCheckboxElm.current;
+      if (input != null) {
+        input.checked = true;
+        input.indeterminate = false;
+      }
+    },
+    deselect: () => {
+      const input = selectAllCheckboxElm.current;
+      if (input != null) {
+        input.checked = false;
+        input.indeterminate = false;
+      }
+    },
+    setIndeterminate: () => {
+      const input = selectAllCheckboxElm.current;
+      if (input != null) {
+        input.indeterminate = true;
+      }
+    },
+  }));
+
   const checkboxChangedHandler = (e: ChangeEvent<HTMLInputElement>) => {
     if (onCheckboxChanged != null) {
       onCheckboxChanged(e.target.checked);
     }
   };
 
-  const selectAllCheckboxElm = useRef<IndeterminateInputElement>(null);
-  useEffect(() => {
-    const checkbox = selectAllCheckboxElm.current;
-    if (checkbox == null) {
-      return;
-    }
-
-
-    switch (checkboxType) {
-      case CheckboxType.NONE_CHECKED:
-        checkbox.indeterminate = false;
-        checkbox.checked = false;
-        break;
-      case CheckboxType.ALL_CHECKED:
-        checkbox.indeterminate = false;
-        checkbox.checked = true;
-        break;
-      case CheckboxType.INDETERMINATE:
-        checkbox.indeterminate = true;
-        break;
-    }
-  }, [checkboxType]);
-
   return (
 
     <div className="d-flex align-items-center">
@@ -93,4 +68,6 @@ export const OperateAllControl :FC<Props> = React.memo((props: Props) => {
     </div>
   );
 
-});
+};
+
+export const OperateAllControl = React.memo(forwardRef(OperateAllControlSubstance));

+ 2 - 2
packages/app/src/components/SearchPage/SearchResultList.tsx

@@ -15,7 +15,7 @@ type Props = {
   pages: IPageWithMeta<IPageSearchMeta>[],
   selectedPageId?: string,
   onPageSelected?: (page?: IPageWithMeta<IPageSearchMeta>) => void,
-  onClickCheckbox?: (pageId: string) => void,
+  onCheckboxChanged?: (isChecked: boolean, pageId: string) => void,
 }
 
 const SearchResultListSubstance: ForwardRefRenderFunction<ISelectableAll, Props> = (props:Props, ref) => {
@@ -93,7 +93,7 @@ const SearchResultListSubstance: ForwardRefRenderFunction<ISelectableAll, Props>
             isEnableActions={isGuestUser}
             isSelected={page.pageData._id === selectedPageId}
             onClickItem={clickItemHandler}
-            onClickCheckbox={props.onClickCheckbox}
+            onCheckboxChanged={props.onCheckboxChanged}
             onClickDeleteButton={clickDeleteButtonHandler}
           />
         );

+ 42 - 16
packages/app/src/components/SearchPage2/SearchPageBase.tsx

@@ -1,5 +1,5 @@
 import React, {
-  FC, forwardRef, ForwardRefRenderFunction, useEffect, useImperativeHandle, useRef, useState,
+  forwardRef, ForwardRefRenderFunction, useEffect, useImperativeHandle, useRef, useState,
 } from 'react';
 import { ISelectableAll } from '~/client/interfaces/selectable-all';
 import AppContainer from '~/client/services/AppContainer';
@@ -15,7 +15,7 @@ type Props = {
 
   pages?: IPageWithMeta<IPageSearchMeta>[],
 
-  onSelectedPagesByCheckboxesChanged?: (selectedCount: number) => void,
+  onSelectedPagesByCheckboxesChanged?: (selectedCount: number, totalCount: number) => void,
 
   searchControl: React.ReactNode,
   searchResultListHead: React.ReactNode,
@@ -32,6 +32,16 @@ const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll, Props> =
 
   const searchResultListRef = useRef<ISelectableAll|null>(null);
 
+  const { data: isGuestUser } = useIsGuestUser();
+
+  // TODO get search keywords and split
+  // ref: RevisionRenderer
+  //   [...keywords.match(/"[^"]+"|[^\u{20}\u{3000}]+/ug)].forEach((keyword, i) => {
+  const [highlightKeywords, setHightlightKeywords] = useState<string[]>([]);
+  const [selectedPageIdsByCheckboxes] = useState<Set<string>>(new Set());
+  // const [allPageIds] = useState<Set<string>>(new Set());
+  const [selectedPageWithMeta, setSelectedPageWithMeta] = useState<IPageWithMeta<IPageSearchMeta> | undefined>();
+
   // publish selectAll()
   useImperativeHandle(ref, () => ({
     selectAll: () => {
@@ -39,34 +49,35 @@ const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll, Props> =
       if (instance != null) {
         instance.selectAll();
       }
+
+      if (pages != null) {
+        pages.forEach(page => selectedPageIdsByCheckboxes.add(page.pageData._id));
+      }
     },
     deselectAll: () => {
       const instance = searchResultListRef.current;
       if (instance != null) {
         instance.deselectAll();
       }
+
+      selectedPageIdsByCheckboxes.clear();
     },
   }));
 
-  const { data: isGuestUser } = useIsGuestUser();
-
-  // TODO get search keywords and split
-  // ref: RevisionRenderer
-  //   [...keywords.match(/"[^"]+"|[^\u{20}\u{3000}]+/ug)].forEach((keyword, i) => {
-  const [highlightKeywords, setHightlightKeywords] = useState<string[]>([]);
-  const [selectedPageIdsByCheckboxes] = useState<Set<string>>(new Set());
-  const [selectedPageWithMeta, setSelectedPageWithMeta] = useState<IPageWithMeta<IPageSearchMeta> | undefined>();
+  const checkboxChangedHandler = (isChecked: boolean, pageId: string) => {
+    if (pages == null || pages.length === 0) {
+      return;
+    }
 
-  const checkboxClickedHandler = (pageId: string) => {
-    if (selectedPageIdsByCheckboxes.has(pageId)) {
-      selectedPageIdsByCheckboxes.delete(pageId);
+    if (isChecked) {
+      selectedPageIdsByCheckboxes.add(pageId);
     }
     else {
-      selectedPageIdsByCheckboxes.add(pageId);
+      selectedPageIdsByCheckboxes.delete(pageId);
     }
 
     if (onSelectedPagesByCheckboxesChanged != null) {
-      onSelectedPagesByCheckboxesChanged(selectedPageIdsByCheckboxes.size);
+      onSelectedPagesByCheckboxesChanged(selectedPageIdsByCheckboxes.size, pages.length);
     }
   };
 
@@ -77,6 +88,21 @@ const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll, Props> =
     }
   }, [pages, selectedPageWithMeta]);
 
+  // reset selectedPageIdsByCheckboxes
+  useEffect(() => {
+    if (pages == null) {
+      return;
+    }
+
+    if (pages.length > 0) {
+      selectedPageIdsByCheckboxes.clear();
+    }
+
+    if (onSelectedPagesByCheckboxesChanged != null) {
+      onSelectedPagesByCheckboxesChanged(selectedPageIdsByCheckboxes.size, pages.length);
+    }
+  }, [onSelectedPagesByCheckboxesChanged, pages, selectedPageIdsByCheckboxes]);
+
   const isLoading = pages == null;
 
   return (
@@ -106,7 +132,7 @@ const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll, Props> =
                     pages={pages}
                     selectedPageId={selectedPageWithMeta?.pageData._id}
                     onPageSelected={page => setSelectedPageWithMeta(page)}
-                    onClickCheckbox={checkboxClickedHandler}
+                    onCheckboxChanged={checkboxChangedHandler}
                   />
                 </div>
                 <div className="my-4 d-flex justify-content-center">

+ 0 - 6
packages/app/src/interfaces/search.ts

@@ -1,11 +1,5 @@
 import { IPageInfoAll, IPageWithMeta } from './page';
 
-export enum CheckboxType {
-  NONE_CHECKED = 'noneChecked',
-  INDETERMINATE = 'indeterminate',
-  ALL_CHECKED = 'allChecked',
-}
-
 export type IPageSearchMeta = {
   bookmarkCount?: number,
   elasticSearchResult?: {