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

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

@@ -0,0 +1,9 @@
+export interface ISelectable {
+  select: () => void,
+  deselect: () => void,
+}
+
+export interface ISelectableAll {
+  selectAll: () => void,
+  deselectAll: () => void,
+}

+ 28 - 3
packages/app/src/components/PageList/PageListItemL.tsx

@@ -1,4 +1,7 @@
-import React, { memo, useCallback } from 'react';
+import React, {
+  forwardRef,
+  ForwardRefRenderFunction, memo, useCallback, useImperativeHandle, useRef,
+} from 'react';
 
 
 import Clamp from 'react-multiline-clamp';
 import Clamp from 'react-multiline-clamp';
 import { format } from 'date-fns';
 import { format } from 'date-fns';
@@ -14,6 +17,7 @@ import { IPageSearchMeta, isIPageSearchMeta } from '~/interfaces/search';
 import { PageItemControl } from '../Common/Dropdown/PageItemControl';
 import { PageItemControl } from '../Common/Dropdown/PageItemControl';
 import LinkedPagePath from '~/models/linked-page-path';
 import LinkedPagePath from '~/models/linked-page-path';
 import PagePathHierarchicalLink from '../PagePathHierarchicalLink';
 import PagePathHierarchicalLink from '../PagePathHierarchicalLink';
+import { ISelectable } from '~/client/interfaces/selectable-all';
 
 
 type Props = {
 type Props = {
   page: IPageWithMeta | IPageWithMeta<IPageInfoAll & IPageSearchMeta>,
   page: IPageWithMeta | IPageWithMeta<IPageInfoAll & IPageSearchMeta>,
@@ -25,13 +29,31 @@ type Props = {
   onClickDeleteButton?: (pageId: string) => void,
   onClickDeleteButton?: (pageId: string) => void,
 }
 }
 
 
-export const PageListItemL = memo((props: Props): JSX.Element => {
+const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (props: Props, ref): JSX.Element => {
   const {
   const {
     // todo: refactoring variable name to clear what changed
     // todo: refactoring variable name to clear what changed
     page: { pageData, pageMeta }, isSelected, onClickItem, onClickCheckbox, isEnableActions,
     page: { pageData, pageMeta }, isSelected, onClickItem, onClickCheckbox, isEnableActions,
     showPageUpdatedTime,
     showPageUpdatedTime,
   } = props;
   } = props;
 
 
+  const inputRef = useRef<HTMLInputElement>(null);
+
+  // publish ISelectable methods
+  useImperativeHandle(ref, () => ({
+    select: () => {
+      const input = inputRef.current;
+      if (input != null) {
+        input.checked = true;
+      }
+    },
+    deselect: () => {
+      const input = inputRef.current;
+      if (input != null) {
+        input.checked = false;
+      }
+    },
+  }));
+
   const { data: isDeviceSmallerThanLg } = useIsDeviceSmallerThanLg();
   const { data: isDeviceSmallerThanLg } = useIsDeviceSmallerThanLg();
 
 
   const elasticSearchResult = isIPageSearchMeta(pageMeta) ? pageMeta.elasticSearchResult : null;
   const elasticSearchResult = isIPageSearchMeta(pageMeta) ? pageMeta.elasticSearchResult : null;
@@ -76,6 +98,7 @@ export const PageListItemL = memo((props: Props): JSX.Element => {
                 className="form-check-input position-relative m-0"
                 className="form-check-input position-relative m-0"
                 type="checkbox"
                 type="checkbox"
                 id="flexCheckDefault"
                 id="flexCheckDefault"
+                ref={inputRef}
                 onChange={() => { onClickCheckbox(pageData._id) }}
                 onChange={() => { onClickCheckbox(pageData._id) }}
               />
               />
             </div>
             </div>
@@ -135,4 +158,6 @@ export const PageListItemL = memo((props: Props): JSX.Element => {
       </div>
       </div>
     </li>
     </li>
   );
   );
-});
+};
+
+export const PageListItemL = memo(forwardRef(PageListItemLSubstance));

+ 25 - 5
packages/app/src/components/SearchPage.tsx

@@ -1,19 +1,21 @@
 import React, {
 import React, {
-  useCallback, useEffect, useMemo, useState,
+  useCallback, useEffect, useMemo, useRef, useState,
 } from 'react';
 } from 'react';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 
 import { parse as parseQuerystring } from 'querystring';
 import { parse as parseQuerystring } from 'querystring';
 
 
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
-import { IFormattedSearchResult } from '~/interfaces/search';
+import { IFormattedSearchResult, IPageSearchMeta } from '~/interfaces/search';
+import { IPageWithMeta } from '~/interfaces/page';
 
 
 import { ISearchConditions, ISearchConfigurations, useSWRxFullTextSearch } from '~/stores/search';
 import { ISearchConditions, ISearchConfigurations, useSWRxFullTextSearch } from '~/stores/search';
 import PaginationWrapper from './PaginationWrapper';
 import PaginationWrapper from './PaginationWrapper';
 import { OperateAllControl, useSelectAll } from './SearchPage/OperateAllControl';
 import { OperateAllControl, useSelectAll } from './SearchPage/OperateAllControl';
 import SearchControl from './SearchPage/SearchControl';
 import SearchControl from './SearchPage/SearchControl';
 
 
-import SearchPageBase from './SearchPage2/SearchPageBase';
+import { SearchPageBase } from './SearchPage2/SearchPageBase';
+import { ISelectableAll } from '~/client/interfaces/selectable-all';
 
 
 
 
 // TODO: replace with "customize:showPageLimitationS"
 // TODO: replace with "customize:showPageLimitationS"
@@ -103,6 +105,8 @@ export const SearchPage = (props: Props): JSX.Element => {
     limit: INITIAL_PAGIONG_SIZE,
     limit: INITIAL_PAGIONG_SIZE,
   });
   });
 
 
+  const searchPageBaseRef = useRef<ISelectableAll|null>(null);
+
   const { data, conditions } = useSWRxFullTextSearch(keyword, {
   const { data, conditions } = useSWRxFullTextSearch(keyword, {
     limit: INITIAL_PAGIONG_SIZE,
     limit: INITIAL_PAGIONG_SIZE,
     ...configurationsByControl,
     ...configurationsByControl,
@@ -119,6 +123,21 @@ export const SearchPage = (props: Props): JSX.Element => {
     setConfigurationsByControl(newConfigurations);
     setConfigurationsByControl(newConfigurations);
   }, []);
   }, []);
 
 
+  const selectAllCheckboxChangedHandler = useCallback((isChecked: boolean) => {
+    const instance = searchPageBaseRef.current;
+
+    if (instance == null) {
+      return;
+    }
+
+    if (isChecked) {
+      instance.selectAll();
+    }
+    if (isChecked) {
+      instance.deselectAll();
+    }
+  }, []);
+
   const pagingNumberChangedHandler = useCallback((activePage: number) => {
   const pagingNumberChangedHandler = useCallback((activePage: number) => {
     const currentLimit = configurationsByPagination.limit ?? INITIAL_PAGIONG_SIZE;
     const currentLimit = configurationsByPagination.limit ?? INITIAL_PAGIONG_SIZE;
     setConfigurationsByPagination({
     setConfigurationsByPagination({
@@ -151,7 +170,7 @@ export const SearchPage = (props: Props): JSX.Element => {
       <OperateAllControl
       <OperateAllControl
         checkboxType={selectAllPagesCheckboxType}
         checkboxType={selectAllPagesCheckboxType}
         isCheckboxDisabled={isDisabled}
         isCheckboxDisabled={isDisabled}
-        onCheckboxChanged={() => null /* TODO implement */}
+        onCheckboxChanged={selectAllCheckboxChangedHandler}
       >
       >
         <button
         <button
           type="button"
           type="button"
@@ -164,7 +183,7 @@ export const SearchPage = (props: Props): JSX.Element => {
         </button>
         </button>
       </OperateAllControl>
       </OperateAllControl>
     );
     );
-  }, [hitsCount, selectAllPagesCheckboxType, t]);
+  }, [hitsCount, selectAllCheckboxChangedHandler, selectAllPagesCheckboxType, t]);
 
 
   const searchControl = useMemo(() => {
   const searchControl = useMemo(() => {
     return (
     return (
@@ -220,6 +239,7 @@ export const SearchPage = (props: Props): JSX.Element => {
 
 
   return (
   return (
     <SearchPageBase
     <SearchPageBase
+      ref={searchPageBaseRef}
       appContainer={appContainer}
       appContainer={appContainer}
       pages={data?.data}
       pages={data?.data}
       onSelectedPagesByCheckboxesChanged={setSelectedPagesCount}
       onSelectedPagesByCheckboxesChanged={setSelectedPagesCount}

+ 28 - 4
packages/app/src/components/SearchPage/SearchResultList.tsx

@@ -1,4 +1,8 @@
-import React, { FC, useCallback } from 'react';
+import React, {
+  forwardRef,
+  ForwardRefRenderFunction, useCallback, useImperativeHandle, useRef,
+} from 'react';
+import { ISelectable, ISelectableAll } from '~/client/interfaces/selectable-all';
 import { IPageWithMeta, isIPageInfoForListing } from '~/interfaces/page';
 import { IPageWithMeta, isIPageInfoForListing } from '~/interfaces/page';
 import { IPageSearchMeta } from '~/interfaces/search';
 import { IPageSearchMeta } from '~/interfaces/search';
 import { useIsGuestUser } from '~/stores/context';
 import { useIsGuestUser } from '~/stores/context';
@@ -14,7 +18,7 @@ type Props = {
   onClickCheckbox?: (pageId: string) => void,
   onClickCheckbox?: (pageId: string) => void,
 }
 }
 
 
-const SearchResultList: FC<Props> = (props:Props) => {
+const SearchResultListSubstance: ForwardRefRenderFunction<ISelectableAll, Props> = (props:Props, ref) => {
   const {
   const {
     pages, selectedPageId,
     pages, selectedPageId,
     onPageSelected,
     onPageSelected,
@@ -27,6 +31,24 @@ const SearchResultList: FC<Props> = (props:Props) => {
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: idToPageInfo } = useSWRxPageInfoForList(pageIdsWithNoSnippet);
   const { data: idToPageInfo } = useSWRxPageInfoForList(pageIdsWithNoSnippet);
 
 
+  const itemsRef = useRef<(ISelectable|null)[]>([]);
+
+  // publish selectAll()
+  useImperativeHandle(ref, () => ({
+    selectAll: () => {
+      const items = itemsRef.current;
+      if (items != null) {
+        items.forEach(item => item != null && item.select());
+      }
+    },
+    deselectAll: () => {
+      const items = itemsRef.current;
+      if (items != null) {
+        items.forEach(item => item != null && item.deselect());
+      }
+    },
+  }));
+
   const clickItemHandler = useCallback((pageId: string) => {
   const clickItemHandler = useCallback((pageId: string) => {
     if (onPageSelected != null) {
     if (onPageSelected != null) {
       const selectedPage = pages.find(page => page.pageData._id === pageId);
       const selectedPage = pages.find(page => page.pageData._id === pageId);
@@ -61,10 +83,12 @@ const SearchResultList: FC<Props> = (props:Props) => {
 
 
   return (
   return (
     <ul className="page-list-ul list-group list-group-flush">
     <ul className="page-list-ul list-group list-group-flush">
-      { (injectedPage ?? pages).map((page) => {
+      { (injectedPage ?? pages).map((page, i) => {
         return (
         return (
           <PageListItemL
           <PageListItemL
             key={page.pageData._id}
             key={page.pageData._id}
+            // eslint-disable-next-line no-return-assign
+            ref={c => itemsRef.current[i] = c}
             page={page}
             page={page}
             isEnableActions={isGuestUser}
             isEnableActions={isGuestUser}
             isSelected={page.pageData._id === selectedPageId}
             isSelected={page.pageData._id === selectedPageId}
@@ -79,4 +103,4 @@ const SearchResultList: FC<Props> = (props:Props) => {
 
 
 };
 };
 
 
-export default SearchResultList;
+export const SearchResultList = forwardRef(SearchResultListSubstance);

+ 24 - 4
packages/app/src/components/SearchPage2/SearchPageBase.tsx

@@ -1,13 +1,14 @@
 import React, {
 import React, {
-  FC, useEffect, useState,
+  FC, forwardRef, ForwardRefRenderFunction, useEffect, useImperativeHandle, useRef, useState,
 } from 'react';
 } from 'react';
+import { ISelectableAll } from '~/client/interfaces/selectable-all';
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
 import { IPageWithMeta } from '~/interfaces/page';
 import { IPageWithMeta } from '~/interfaces/page';
 import { IPageSearchMeta } from '~/interfaces/search';
 import { IPageSearchMeta } from '~/interfaces/search';
 import { useIsGuestUser } from '~/stores/context';
 import { useIsGuestUser } from '~/stores/context';
 
 
 import { SearchResultContent } from '../SearchPage/SearchResultContent';
 import { SearchResultContent } from '../SearchPage/SearchResultContent';
-import SearchResultList from '../SearchPage/SearchResultList';
+import { SearchResultList } from '../SearchPage/SearchResultList';
 
 
 type Props = {
 type Props = {
   appContainer: AppContainer,
   appContainer: AppContainer,
@@ -21,7 +22,7 @@ type Props = {
   searchPager: React.ReactNode,
   searchPager: React.ReactNode,
 }
 }
 
 
-const SearchPageBase: FC<Props> = (props: Props) => {
+const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll, Props> = (props:Props, ref) => {
   const {
   const {
     appContainer,
     appContainer,
     pages,
     pages,
@@ -29,6 +30,24 @@ const SearchPageBase: FC<Props> = (props: Props) => {
     searchControl, searchResultListHead, searchPager,
     searchControl, searchResultListHead, searchPager,
   } = props;
   } = props;
 
 
+  const searchResultListRef = useRef<ISelectableAll|null>(null);
+
+  // publish selectAll()
+  useImperativeHandle(ref, () => ({
+    selectAll: () => {
+      const instance = searchResultListRef.current;
+      if (instance != null) {
+        instance.selectAll();
+      }
+    },
+    deselectAll: () => {
+      const instance = searchResultListRef.current;
+      if (instance != null) {
+        instance.deselectAll();
+      }
+    },
+  }));
+
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isGuestUser } = useIsGuestUser();
 
 
   // TODO get search keywords and split
   // TODO get search keywords and split
@@ -83,6 +102,7 @@ const SearchPageBase: FC<Props> = (props: Props) => {
                 </div>
                 </div>
                 <div className="page-list px-md-4">
                 <div className="page-list px-md-4">
                   <SearchResultList
                   <SearchResultList
+                    ref={searchResultListRef}
                     pages={pages}
                     pages={pages}
                     selectedPageId={selectedPageWithMeta?.pageData._id}
                     selectedPageId={selectedPageWithMeta?.pageData._id}
                     onPageSelected={page => setSelectedPageWithMeta(page)}
                     onPageSelected={page => setSelectedPageWithMeta(page)}
@@ -116,4 +136,4 @@ const SearchPageBase: FC<Props> = (props: Props) => {
 };
 };
 
 
 
 
-export default SearchPageBase;
+export const SearchPageBase = forwardRef(SearchPageBaseSubstance);