Procházet zdrojové kódy

refactor some modals

Yuki Takei před 5 měsíci
rodič
revize
3d89721271

+ 5 - 0
apps/app/src/client/components/DeleteBookmarkFolderModal.tsx

@@ -40,6 +40,11 @@ const DeleteBookmarkFolderModal: FC = () => {
     await deleteBookmark();
   }, [deleteBookmark]);
 
+  // Early return optimization
+  if (!isOpened || bookmarkFolder == null) {
+    return <></>;
+  }
+
   return (
     <Modal size="md" isOpen={isOpened} toggle={closeBookmarkFolderDeleteModal} data-testid="page-delete-modal" className="grw-create-page">
       <ModalHeader tag="h4" toggle={closeBookmarkFolderDeleteModal} className="text-danger">

+ 9 - 3
apps/app/src/client/components/EmptyTrashModal.tsx

@@ -1,5 +1,5 @@
 import type { FC } from 'react';
-import React, { useState, useCallback } from 'react';
+import React, { useState, useCallback, useMemo } from 'react';
 
 import { useTranslation } from 'next-i18next';
 import {
@@ -43,7 +43,8 @@ const EmptyTrashModal: FC = () => {
     await emptyTrash();
   }, [emptyTrash]);
 
-  const renderPagePaths = useCallback(() => {
+  // Memoize page paths rendering
+  const renderPagePaths = useMemo(() => {
     if (pages != null) {
       return pages.map(page => (
         <p key={page.data._id} className="mb-1">
@@ -54,6 +55,11 @@ const EmptyTrashModal: FC = () => {
     return <></>;
   }, [pages]);
 
+  // Early return optimization
+  if (!isOpened) {
+    return <></>;
+  }
+
   return (
     <Modal size="lg" isOpen={isOpened} toggle={closeEmptyTrashModal} data-testid="page-delete-modal">
       <ModalHeader tag="h4" toggle={closeEmptyTrashModal} className="text-danger">
@@ -64,7 +70,7 @@ const EmptyTrashModal: FC = () => {
         <div className="grw-scrollable-modal-body pb-1">
           <label className="form-label">{ t('modal_delete.deleting_page') }:</label><br />
           {/* Todo: change the way to show path on modal when too many pages are selected */}
-          {renderPagePaths()}
+          {renderPagePaths}
         </div>
         {!canDeleteAllpages && t('modal_empty.not_deletable_notice')}<br />
         {t('modal_empty.notice')}

+ 22 - 22
apps/app/src/client/components/ShortcutsModal.tsx

@@ -1,4 +1,4 @@
-import React, { type JSX } from 'react';
+import React, { useMemo } from 'react';
 
 import { useTranslation } from 'next-i18next';
 import { Modal, ModalHeader, ModalBody } from 'reactstrap';
@@ -8,22 +8,21 @@ import { useShortcutsModalStatus, useShortcutsModalActions } from '~/states/ui/m
 import styles from './ShortcutsModal.module.scss';
 
 
-const ShortcutsModal = (): JSX.Element => {
+const ShortcutsModal = (): React.JSX.Element => {
   const { t } = useTranslation();
 
   const status = useShortcutsModalStatus();
   const { close } = useShortcutsModalActions();
 
-  const bodyContent = () => {
-    if (status == null || !status.isOpened) {
-      return <></>;
-    }
-
-    // add classes to cmd-key by OS
+  // Memoize OS-specific class
+  const additionalClassByOs = useMemo(() => {
     const platform = window.navigator.platform.toLowerCase();
     const isMac = (platform.indexOf('mac') > -1);
-    const additionalClassByOs = isMac ? 'mac' : 'win';
+    return isMac ? 'mac' : 'win';
+  }, []);
 
+  // Memoize body content (large static JSX)
+  const bodyContent = useMemo(() => {
     return (
       <div className="container">
         <div className="row">
@@ -399,21 +398,22 @@ const ShortcutsModal = (): JSX.Element => {
         </div>
       </div>
     );
-  };
+  }, [additionalClassByOs, t]);
+
+  // Early return optimization
+  if (status == null || !status.isOpened) {
+    return <></>;
+  }
 
   return (
-    <>
-      { status != null && (
-        <Modal id="shortcuts-modal" size="lg" isOpen={status.isOpened} toggle={close} className={`shortcuts-modal ${styles['shortcuts-modal']}`}>
-          <ModalHeader tag="h4" toggle={close} className="px-4">
-            {t('Shortcuts')}
-          </ModalHeader>
-          <ModalBody className="p-md-4 mb-3 grw-modal-body-style overflow-auto">
-            {bodyContent()}
-          </ModalBody>
-        </Modal>
-      ) }
-    </>
+    <Modal id="shortcuts-modal" size="lg" isOpen={status.isOpened} toggle={close} className={`shortcuts-modal ${styles['shortcuts-modal']}`}>
+      <ModalHeader tag="h4" toggle={close} className="px-4">
+        {t('Shortcuts')}
+      </ModalHeader>
+      <ModalBody className="p-md-4 mb-3 grw-modal-body-style overflow-auto">
+        {bodyContent}
+      </ModalBody>
+    </Modal>
   );
 };
 

+ 5 - 0
apps/app/src/features/growi-plugin/client/Admin/components/PluginsExtensionPageContents/PluginDeleteModal.tsx

@@ -41,6 +41,11 @@ export const PluginDeleteModal: React.FC = () => {
     }
   }, [id, closePluginDeleteModal, t, mutate]);
 
+  // Early return optimization
+  if (!isOpen) {
+    return <></>;
+  }
+
   return (
     <Modal isOpen={isOpen} toggle={toggleHandler}>
       <ModalHeader

+ 27 - 14
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/SelectUserGroupModal.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, useMemo } from 'react';
 
 import { GroupType } from '@growi/core';
 import { useTranslation } from 'react-i18next';
@@ -31,21 +31,29 @@ const SelectUserGroupModalSubstance: React.FC<Props> = (props: Props) => {
     return selectedUserGroupIds.includes(targetUserGroup.item._id);
   }, [selectedUserGroups]);
 
+  // Memoize user group list
+  const userGroupList = useMemo(() => {
+    if (userRelatedGroups == null) {
+      return null;
+    }
+    return userRelatedGroups.map(userGroup => (
+      <button
+        className="btn btn-outline-primary d-flex justify-content-start mb-3 mx-4 align-items-center p-3"
+        type="button"
+        key={userGroup.item._id}
+        onClick={() => onSelect(userGroup)}
+      >
+        <input type="checkbox" checked={checked(userGroup)} onChange={() => {}} />
+        <p className="ms-3 mb-0">{userGroup.item.name}</p>
+        {userGroup.type === GroupType.externalUserGroup && <span className="ms-2 badge badge-pill badge-info">{userGroup.item.provider}</span>}
+        {/* TODO: Replace <div className="small">(TBD) List group members</div> */}
+      </button>
+    ));
+  }, [userRelatedGroups, onSelect, checked]);
+
   return (
     <ModalBody className="d-flex flex-column">
-      {userRelatedGroups != null && userRelatedGroups.map(userGroup => (
-        <button
-          className="btn btn-outline-primary d-flex justify-content-start mb-3 mx-4 align-items-center p-3"
-          type="button"
-          key={userGroup.item._id}
-          onClick={() => onSelect(userGroup)}
-        >
-          <input type="checkbox" checked={checked(userGroup)} onChange={() => {}} />
-          <p className="ms-3 mb-0">{userGroup.item.name}</p>
-          {userGroup.type === GroupType.externalUserGroup && <span className="ms-2 badge badge-pill badge-info">{userGroup.item.provider}</span>}
-          {/* TODO: Replace <div className="small">(TBD) List group members</div> */}
-        </button>
-      ))}
+      {userGroupList}
       <button
         type="button"
         className="btn btn-primary mt-2 mx-auto"
@@ -63,6 +71,11 @@ export const SelectUserGroupModal: React.FC<Props> = (props) => {
 
   const { isOpen, closeModal } = props;
 
+  // Early return optimization
+  if (!isOpen) {
+    return <></>;
+  }
+
   return (
     <Modal isOpen={isOpen} toggle={closeModal}>
       <ModalHeader toggle={closeModal}>

+ 17 - 7
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/ShareScopeWarningModal.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback, type JSX } from 'react';
+import React, { useCallback, useMemo } from 'react';
 
 import { useTranslation } from 'react-i18next';
 import {
@@ -14,7 +14,7 @@ type Props = {
   onSubmit: () => Promise<void>,
 }
 
-export const ShareScopeWarningModal = (props: Props): JSX.Element => {
+export const ShareScopeWarningModal = (props: Props): React.JSX.Element => {
   const {
     isOpen,
     selectedPages,
@@ -29,6 +29,20 @@ export const ShareScopeWarningModal = (props: Props): JSX.Element => {
     onSubmit();
   }, [closeModal, onSubmit]);
 
+  // Memoize selected pages list
+  const selectedPagesList = useMemo(() => {
+    return selectedPages.map(selectedPage => (
+      <code key={selectedPage.path}>
+        {selectedPage.path}
+      </code>
+    ));
+  }, [selectedPages]);
+
+  // Early return optimization
+  if (!isOpen) {
+    return <></>;
+  }
+
   return (
     <Modal size="lg" isOpen={isOpen} toggle={closeModal}>
       <ModalHeader toggle={closeModal}>
@@ -47,11 +61,7 @@ export const ShareScopeWarningModal = (props: Props): JSX.Element => {
 
         <div className="mb-4">
           <p className="mb-2 text-secondary">{t('share_scope_warning_modal.selected_pages_label')}</p>
-          {selectedPages.map(selectedPage => (
-            <code key={selectedPage.path}>
-              {selectedPage.path}
-            </code>
-          ))}
+          {selectedPagesList}
         </div>
 
         <p>