Taichi Masuyama 3 лет назад
Родитель
Сommit
91a26745cd

+ 53 - 29
packages/app/src/components/Admin/App/FileUploadSetting.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { ChangeEvent, useCallback, useState } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
@@ -11,31 +11,17 @@ import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 import AwsSetting from './AwsSetting';
 import GcsSettings from './GcsSettings';
 
+const fileUploadTypes = ['aws', 'gcs', 'gridfs', 'local'] as const;
 
-type Props = {
-  adminAppContainer: AdminAppContainer,
+type FileUploadSettingMoleculeProps = {
+  fileUploadType: string
+  isFixedFileUploadByEnvVar: boolean
+  envFileUploadType?: string
+  onChangeFileUploadType: (e: ChangeEvent, type: string) => void
 }
 
-
-const FileUploadSetting = (props: Props): JSX.Element => {
+export const FileUploadSettingMolecule = React.memo((props: FileUploadSettingMoleculeProps): JSX.Element => {
   const { t } = useTranslation(['admin', 'commons']);
-  const { adminAppContainer } = props;
-
-  const {
-    fileUploadType, isFixedFileUploadByEnvVar, envFileUploadType, retrieveError,
-  } = adminAppContainer.state;
-
-  const fileUploadTypes = ['aws', 'gcs', 'gridfs', 'local'];
-
-  const submitHandler = useCallback(async() => {
-    try {
-      await adminAppContainer.updateFileUploadSettingHandler();
-      toastSuccess(t('toaster.update_successed', { target: t('admin:app_setting.file_upload_settings'), ns: 'commons' }));
-    }
-    catch (err) {
-      toastError(err);
-    }
-  }, [adminAppContainer, t]);
 
   return (
     <>
@@ -63,28 +49,66 @@ const FileUploadSetting = (props: Props): JSX.Element => {
                   className="custom-control-input"
                   name="file-upload-type"
                   id={`file-upload-type-radio-${type}`}
-                  checked={fileUploadType === type}
-                  disabled={isFixedFileUploadByEnvVar}
-                  onChange={() => { adminAppContainer.changeFileUploadType(type) }}
+                  checked={props.fileUploadType === type}
+                  disabled={props.isFixedFileUploadByEnvVar}
+                  onChange={(e) => { props?.onChangeFileUploadType(e, type) }}
                 />
                 <label className="custom-control-label" htmlFor={`file-upload-type-radio-${type}`}>{t(`admin:app_setting.${type}_label`)}</label>
               </div>
             );
           })}
         </div>
-        {isFixedFileUploadByEnvVar && (
+        {props.isFixedFileUploadByEnvVar && (
           <p className="alert alert-warning mt-2 text-left offset-3 col-6">
             <i className="icon-exclamation icon-fw">
             </i><b>FIXED</b><br />
             {/* eslint-disable-next-line react/no-danger */}
-            <b dangerouslySetInnerHTML={{ __html: t('admin:app_setting.fixed_by_env_var', { fileUploadType: envFileUploadType }) }} />
+            <b dangerouslySetInnerHTML={{ __html: t('admin:app_setting.fixed_by_env_var', { fileUploadType: props.envFileUploadType }) }} />
           </p>
         )}
       </div>
 
-      {fileUploadType === 'aws' && <AwsSetting />}
-      {fileUploadType === 'gcs' && <GcsSettings />}
+      {props.fileUploadType === 'aws' && <AwsSetting />}
+      {props.fileUploadType === 'gcs' && <GcsSettings />}
+    </>
+  );
+});
+FileUploadSettingMolecule.displayName = 'FileUploadSettingMolecule';
+
+
+type FileUploadSettingProps = {
+  adminAppContainer: AdminAppContainer
+}
+
+const FileUploadSetting = (props: FileUploadSettingProps): JSX.Element => {
+  const { t } = useTranslation(['admin', 'commons']);
+  const { adminAppContainer } = props;
 
+  const {
+    fileUploadType, isFixedFileUploadByEnvVar, envFileUploadType, retrieveError,
+  } = adminAppContainer.state;
+
+  const submitHandler = useCallback(async() => {
+    try {
+      await adminAppContainer.updateFileUploadSettingHandler();
+      toastSuccess(t('toaster.update_successed', { target: t('admin:app_setting.file_upload_settings'), ns: 'commons' }));
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }, [adminAppContainer, t]);
+
+  const onChangeFileUploadTypeHandler = useCallback((e: ChangeEvent, type: string) => {
+    adminAppContainer.changeFileUploadType(type);
+  }, [adminAppContainer]);
+
+  return (
+    <>
+      <FileUploadSettingMolecule
+        fileUploadType={fileUploadType}
+        isFixedFileUploadByEnvVar={isFixedFileUploadByEnvVar}
+        envFileUploadType={envFileUploadType}
+        onChangeFileUploadType={onChangeFileUploadTypeHandler}/>
       <AdminUpdateButtonRow onClick={submitHandler} disabled={retrieveError != null} />
     </>
   );

+ 17 - 2
packages/app/src/components/Admin/G2GDataTransfer.tsx

@@ -1,4 +1,6 @@
-import React, { useCallback, useEffect, useState } from 'react';
+import React, {
+  ChangeEvent, useCallback, useEffect, useState,
+} from 'react';
 
 import { useTranslation } from 'next-i18next';
 
@@ -10,6 +12,7 @@ import { useAdminSocket } from '~/stores/socket-io';
 
 import CustomCopyToClipBoard from '../Common/CustomCopyToClipBoard';
 
+import { FileUploadSettingMolecule } from './App/FileUploadSetting';
 import G2GDataTransferExportForm from './G2GDataTransferExportForm';
 import G2GDataTransferStatusIcon from './G2GDataTransferStatusIcon';
 
@@ -31,6 +34,7 @@ const G2GDataTransfer = (): JSX.Element => {
     mongo: G2G_PROGRESS_STATUS.PENDING,
     attachments: G2G_PROGRESS_STATUS.PENDING,
   });
+  const [fileUploadType, setFileUploadType] = useState('aws');
 
   const updateSelectedCollections = (newSelectedCollections: Set<string>) => {
     setSelectedCollections(newSelectedCollections);
@@ -107,6 +111,10 @@ const G2GDataTransfer = (): JSX.Element => {
     }
   }, [setTransferring, startTransferKey, selectedCollections, optionsMap]);
 
+  const onChangeFileUploadTypeHandler = useCallback((e: ChangeEvent, type: string) => {
+    setFileUploadType(type);
+  }, []);
+
   useEffect(() => {
     setCollectionsAndSelectedCollections();
     setupWebsocketEventHandler();
@@ -125,7 +133,14 @@ const G2GDataTransfer = (): JSX.Element => {
       </button>
 
       {collections.length !== 0 && (
-        <div className={isShowExportForm ? '' : 'd-none'}>
+        <div className={`${isShowExportForm ? '' : 'd-none'} px-3 pt-3`}>
+          <h3 className='mb-1'>ファイルアップロード設定</h3>
+          <FileUploadSettingMolecule
+            fileUploadType={fileUploadType}
+            isFixedFileUploadByEnvVar={false}
+            onChangeFileUploadType={onChangeFileUploadTypeHandler}
+          />
+          <h3 className='mb-1'>エクスポート設定</h3>
           <G2GDataTransferExportForm
             allCollectionNames={collections}
             selectedCollections={selectedCollections}

+ 6 - 70
packages/app/src/components/Admin/G2GDataTransferExportForm.tsx

@@ -7,7 +7,6 @@ import { useTranslation } from 'next-i18next';
 import GrowiArchiveImportOption from '~/models/admin/growi-archive-import-option';
 import ImportOptionForPages from '~/models/admin/import-option-for-pages';
 import ImportOptionForRevisions from '~/models/admin/import-option-for-revisions';
-// import { useAdminSocket } from '~/stores/socket-io';
 
 import ImportCollectionConfigurationModal from './ImportData/GrowiArchive/ImportCollectionConfigurationModal';
 import ImportCollectionItem, { DEFAULT_MODE, MODE_RESTRICTED_COLLECTION } from './ImportData/GrowiArchive/ImportCollectionItem';
@@ -37,17 +36,12 @@ type Props = {
 };
 
 const G2GDataTransferExportForm = (props: Props): JSX.Element => {
-  // const { data: socket } = useAdminSocket();
   const { t } = useTranslation('admin');
 
   const {
     allCollectionNames, selectedCollections, updateSelectedCollections, optionsMap, updateOptionsMap,
   } = props;
 
-  // const [isImporting, setImporting] = useState(false);
-  // const [isImported, setImported] = useState(false);
-  // const [progressMap, setProgressMap] = useState<any>({});
-  // const [errorsMap, setErrorsMap] = useState<any>([]);
   const [isConfigurationModalOpen, setConfigurationModalOpen] = useState(false);
   const [collectionNameForConfiguration, setCollectionNameForConfiguration] = useState<any>();
 
@@ -59,7 +53,7 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
     updateSelectedCollections(new Set());
   }, [updateSelectedCollections]);
 
-  const updateOption = (collectionName, data) => {
+  const updateOption = useCallback((collectionName, data) => {
     const options = optionsMap[collectionName];
 
     // merge
@@ -70,7 +64,7 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
     updateOptionsMap((prev) => {
       return { ...prev, updatedOptionsMap };
     });
-  };
+  }, [optionsMap, updateOptionsMap]);
 
   const ImportItems = ({ collectionNames }): JSX.Element => {
     const toggleCheckbox = (collectionName, bool) => {
@@ -96,8 +90,6 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
     return (
       <div className="row">
         {collectionNames.map((collectionName) => {
-          // const collectionProgress = progressMap[collectionName];
-          // const errors = errorsMap[collectionName];
           const isConfigButtonAvailable = Object.keys(IMPORT_OPTION_CLASS_MAPPING).includes(collectionName);
 
           if (optionsMap[collectionName] == null) {
@@ -107,11 +99,6 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
           return (
             <div className="col-md-6 my-1" key={collectionName}>
               <ImportCollectionItem
-                // isImporting={isImporting}
-                // isImported={collectionProgress ? isImported : false}
-                // insertedCount={collectionProgress ? collectionProgress.insertedCount : 0}
-                // modifiedCount={collectionProgress ? collectionProgress.modifiedCount : 0}
-                // errorsCount={errors ? errors.length : 0}
                 isImporting={false}
                 isImported={false}
                 insertedCount={0}
@@ -192,9 +179,9 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
         option={optionsMap[collectionNameForConfiguration]}
       />
     );
-  }, [collectionNameForConfiguration, isConfigurationModalOpen]);
+  }, [collectionNameForConfiguration, isConfigurationModalOpen, optionsMap, updateOption]);
 
-  const setInitialOptionsMap = () => {
+  const setInitialOptionsMap = useCallback(() => {
     const initialOptionsMap = {};
     allCollectionNames.forEach((collectionName) => {
       const initialMode = (MODE_RESTRICTED_COLLECTION[collectionName] != null)
@@ -204,66 +191,15 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
       initialOptionsMap[collectionName] = new ImportOption(initialMode);
     });
     updateOptionsMap(initialOptionsMap);
-  };
-
-  // TODO: use Socket
-
-  // setupWebsocketEventHandler() {
-  //   const socket = this.props.adminSocketIoContainer.getSocket();
-
-  //   // websocket event
-  //   // eslint-disable-next-line object-curly-newline
-  //   socket.on('admin:onProgressForImport', ({ collectionName, collectionProgress, appendedErrors }) => {
-  //     const { progressMap, errorsMap } = this.state;
-  //     progressMap[collectionName] = collectionProgress;
-
-  //     const errors = errorsMap[collectionName] || [];
-  //     errorsMap[collectionName] = errors.concat(appendedErrors);
-
-  //     this.setState({
-  //       isImporting: true,
-  //       progressMap,
-  //       errorsMap,
-  //     });
-  //   });
-
-  //   // websocket event
-  //   socket.on('admin:onTerminateForImport', () => {
-  //     this.setState({
-  //       isImporting: false,
-  //       isImported: true,
-  //     });
-
-  //     toastSuccess(undefined, 'Import process has completed.');
-  //   });
-
-  //   // websocket event
-  //   socket.on('admin:onErrorForImport', (err) => {
-  //     this.setState({
-  //       isImporting: false,
-  //       isImported: false,
-  //     });
-
-  //     toastError(err, 'Import process has failed.');
-  //   });
-  // }
-
-  // teardownWebsocketEventHandler() {
-  //   const socket = this.props.adminSocketIoContainer.getSocket();
-
-  //   socket.removeAllListeners('admin:onProgressForImport');
-  //   socket.removeAllListeners('admin:onTerminateForImport');
-  // }
+  }, [allCollectionNames, updateOptionsMap]);
 
   useEffect(() => {
     setInitialOptionsMap();
-    // setupWebsocketEventHandler();
-    // teardownWebsocketEventHandler();
   }, []);
 
   return (
     <>
-      <form className="form-inline mt-4">
+      <form className="form-inline mt-3">
         <div className="form-group">
           <button type="button" className="btn btn-sm btn-outline-secondary mr-2" onClick={checkAll}>
             <i className="fa fa-check-square-o"></i> {t('admin:export_management.check_all')}

+ 17 - 6
packages/app/src/pages/admin/data-transfer.page.tsx

@@ -1,10 +1,13 @@
+import { isClient } from '@growi/core';
 import {
   NextPage, GetServerSideProps, GetServerSidePropsContext,
 } from 'next';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { Container, Provider } from 'unstated';
 
+import AdminAppContainer from '~/client/services/AdminAppContainer';
 import { CommonProps } from '~/pages/utils/commons';
 import { useCurrentUser } from '~/stores/context';
 
@@ -23,13 +26,21 @@ const DataTransferPage: NextPage<Props> = (props) => {
 
   const title = t('g2g_data_transfer.data_transfer');
 
+  const injectableContainers: Container<any>[] = [];
+  if (isClient()) {
+    const adminAppContainer = new AdminAppContainer();
+    injectableContainers.push(adminAppContainer);
+  }
+
   return (
-    <AdminLayout componentTitle={title}>
-      <Head>
-        <title>{title}</title>
-      </Head>
-      <G2GDataTransferPage />
-    </AdminLayout>
+    <Provider inject={[...injectableContainers]}>
+      <AdminLayout componentTitle={title}>
+        <Head>
+          <title>{title}</title>
+        </Head>
+        <G2GDataTransferPage />
+      </AdminLayout>
+    </Provider>
   );
 };