Browse Source

impl useWaitingSaveProcessing

Yuki Takei 3 years ago
parent
commit
61494c1f5e

+ 11 - 3
apps/app/src/components/PageEditor.tsx

@@ -29,6 +29,7 @@ import {
   useIsEnabledUnsavedWarning,
   useIsEnabledUnsavedWarning,
   useIsConflict,
   useIsConflict,
   useEditingMarkdown,
   useEditingMarkdown,
+  useWaitingSaveProcessing,
 } from '~/stores/editor';
 } from '~/stores/editor';
 import { useConflictDiffModal } from '~/stores/modal';
 import { useConflictDiffModal } from '~/stores/modal';
 import {
 import {
@@ -76,7 +77,7 @@ const PageEditor = React.memo((): JSX.Element => {
   const { t } = useTranslation();
   const { t } = useTranslation();
   const router = useRouter();
   const router = useRouter();
 
 
-  const { data: isNotFound, mutate: mutateIsNotFound } = useIsNotFound();
+  const { data: isNotFound } = useIsNotFound();
   const { data: pageId, mutate: mutateCurrentPageId } = useCurrentPageId();
   const { data: pageId, mutate: mutateCurrentPageId } = useCurrentPageId();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: currentPathname } = useCurrentPathname();
   const { data: currentPathname } = useCurrentPathname();
@@ -89,6 +90,7 @@ const PageEditor = React.memo((): JSX.Element => {
   const { data: isEnabledAttachTitleHeader } = useIsEnabledAttachTitleHeader();
   const { data: isEnabledAttachTitleHeader } = useIsEnabledAttachTitleHeader();
   const { data: templateBodyData } = useTemplateBodyData();
   const { data: templateBodyData } = useTemplateBodyData();
   const { data: isEditable } = useIsEditable();
   const { data: isEditable } = useIsEditable();
+  const { mutate: mutateWaitingSaveProcessing } = useWaitingSaveProcessing();
   const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
   const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
   const { data: isSlackEnabled } = useIsSlackEnabled();
   const { data: isSlackEnabled } = useIsSlackEnabled();
   const { data: isTextlintEnabled } = useIsTextlintEnabled();
   const { data: isTextlintEnabled } = useIsTextlintEnabled();
@@ -201,6 +203,8 @@ const PageEditor = React.memo((): JSX.Element => {
     const options = Object.assign(optionsToSave, opts);
     const options = Object.assign(optionsToSave, opts);
 
 
     try {
     try {
+      mutateWaitingSaveProcessing(true);
+
       const { page } = await saveOrUpdate(
       const { page } = await saveOrUpdate(
         markdownToSave.current,
         markdownToSave.current,
         { pageId, path: currentPagePath || currentPathname, revisionId: currentRevisionId },
         { pageId, path: currentPagePath || currentPathname, revisionId: currentRevisionId },
@@ -223,10 +227,14 @@ const PageEditor = React.memo((): JSX.Element => {
       }
       }
       return null;
       return null;
     }
     }
+    finally {
+      mutateWaitingSaveProcessing(false);
+    }
 
 
   }, [
   }, [
     currentPathname, optionsToSave, grantData, isSlackEnabled, saveOrUpdate, pageId,
     currentPathname, optionsToSave, grantData, isSlackEnabled, saveOrUpdate, pageId,
-    currentPagePath, currentRevisionId, mutateRemotePageId, mutateRemoteRevisionId, mutateRemoteRevisionLastUpdatedAt, mutateRemoteRevisionLastUpdateUser,
+    currentPagePath, currentRevisionId,
+    mutateWaitingSaveProcessing, mutateRemotePageId, mutateRemoteRevisionId, mutateRemoteRevisionLastUpdatedAt, mutateRemoteRevisionLastUpdateUser,
   ]);
   ]);
 
 
   const saveAndReturnToViewHandler = useCallback(async(opts: {slackChannels: string, overwriteScopesOfDescendants?: boolean}) => {
   const saveAndReturnToViewHandler = useCallback(async(opts: {slackChannels: string, overwriteScopesOfDescendants?: boolean}) => {
@@ -331,7 +339,7 @@ const PageEditor = React.memo((): JSX.Element => {
     finally {
     finally {
       editorRef.current.terminateUploadingState();
       editorRef.current.terminateUploadingState();
     }
     }
-  }, [currentPagePath, mutateCurrentPage, mutateCurrentPageId, mutateGrant, mutateIsLatestRevision, mutateIsNotFound, pageId]);
+  }, [currentPagePath, mutateCurrentPage, mutateCurrentPageId, mutateGrant, mutateIsLatestRevision, pageId]);
 
 
 
 
   const scrollPreviewByEditorLine = useCallback((line: number) => {
   const scrollPreviewByEditorLine = useCallback((line: number) => {

+ 24 - 3
apps/app/src/components/PageEditorByHackmd.tsx

@@ -19,7 +19,7 @@ import {
   useCurrentPathname, useHackmdUri,
   useCurrentPathname, useHackmdUri,
 } from '~/stores/context';
 } from '~/stores/context';
 import {
 import {
-  useIsSlackEnabled, usePageTagsForEditors, useIsEnabledUnsavedWarning,
+  useIsSlackEnabled, usePageTagsForEditors, useIsEnabledUnsavedWarning, useWaitingSaveProcessing,
 } from '~/stores/editor';
 } from '~/stores/editor';
 import {
 import {
   usePageIdOnHackmd, useHasDraftOnHackmd, useRevisionIdHackmdSynced, useIsHackmdDraftUpdatingInRealtime,
   usePageIdOnHackmd, useHasDraftOnHackmd, useRevisionIdHackmdSynced, useIsHackmdDraftUpdatingInRealtime,
@@ -56,6 +56,7 @@ export const PageEditorByHackmd = (): JSX.Element => {
   const router = useRouter();
   const router = useRouter();
 
 
   const { data: isNotFound } = useIsNotFound();
   const { data: isNotFound } = useIsNotFound();
+  const { mutate: mutateWaitingSaveProcessing } = useWaitingSaveProcessing();
   const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
   const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: currentPathname } = useCurrentPathname();
   const { data: currentPathname } = useCurrentPathname();
@@ -116,6 +117,8 @@ export const PageEditorByHackmd = (): JSX.Element => {
         throw new Error('Some materials to save are invalid');
         throw new Error('Some materials to save are invalid');
       }
       }
 
 
+      mutateWaitingSaveProcessing(true);
+
       const options = Object.assign(optionsToSave, opts, { isSyncRevisionToHackmd: true });
       const options = Object.assign(optionsToSave, opts, { isSyncRevisionToHackmd: true });
 
 
       const markdown = await hackmdEditorRef.current.getValue();
       const markdown = await hackmdEditorRef.current.getValue();
@@ -142,8 +145,16 @@ export const PageEditorByHackmd = (): JSX.Element => {
       logger.error('failed to save', error);
       logger.error('failed to save', error);
       toastError(error.message);
       toastError(error.message);
     }
     }
+    finally {
+      mutateWaitingSaveProcessing(false);
+    }
+
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  }, [editorMode, currentPathname, revision, revisionIdHackmdSynced, optionsToSave, saveOrUpdate, pageId, currentPagePath, isNotFound, mutateEditorMode, router, updateStateAfterSave, mutateIsHackmdDraftUpdatingInRealtime]);
+  }, [
+    pageId, currentPagePath, isNotFound, router,
+    editorMode, currentPathname, revision, revisionIdHackmdSynced, optionsToSave,
+    saveOrUpdate, mutateEditorMode, updateStateAfterSave, mutateIsHackmdDraftUpdatingInRealtime, mutateWaitingSaveProcessing,
+  ]);
 
 
   // set handler to save and reload Page
   // set handler to save and reload Page
   useEffect(() => {
   useEffect(() => {
@@ -249,6 +260,8 @@ export const PageEditorByHackmd = (): JSX.Element => {
    */
    */
   const onSaveWithShortcut = useCallback(async(markdown) => {
   const onSaveWithShortcut = useCallback(async(markdown) => {
     try {
     try {
+      mutateWaitingSaveProcessing(true);
+
       const currentPagePathOrPathname = currentPagePath || currentPathname;
       const currentPagePathOrPathname = currentPagePath || currentPathname;
       if (
       if (
         pageId == null || revisionIdHackmdSynced == null || currentPagePathOrPathname == null || optionsToSave == null
         pageId == null || revisionIdHackmdSynced == null || currentPagePathOrPathname == null || optionsToSave == null
@@ -278,8 +291,16 @@ export const PageEditorByHackmd = (): JSX.Element => {
       logger.error('failed to save', error);
       logger.error('failed to save', error);
       toastError(error.message);
       toastError(error.message);
     }
     }
+    finally {
+      mutateWaitingSaveProcessing(false);
+    }
+
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  }, [currentPagePath, currentPathname, pageId, revisionIdHackmdSynced, optionsToSave, saveOrUpdate, mutatePageData, updateStateAfterSave, mutateTagsInfo, mutateIsEnabledUnsavedWarning, t]);
+  }, [
+    currentPagePath, currentPathname, pageId, revisionIdHackmdSynced, optionsToSave,
+    saveOrUpdate,
+    mutateWaitingSaveProcessing, mutatePageData, updateStateAfterSave, mutateTagsInfo, mutateIsEnabledUnsavedWarning, t,
+  ]);
 
 
   /**
   /**
    * onChange event of HackmdEditor handler
    * onChange event of HackmdEditor handler

+ 14 - 2
apps/app/src/components/SavePageControls.tsx

@@ -13,6 +13,7 @@ import { IPageGrantData } from '~/interfaces/page';
 import {
 import {
   useIsEditable, useIsAclEnabled,
   useIsEditable, useIsAclEnabled,
 } from '~/stores/context';
 } from '~/stores/context';
+import { useWaitingSaveProcessing } from '~/stores/editor';
 import { useCurrentPagePath, useCurrentPageId } from '~/stores/page';
 import { useCurrentPagePath, useCurrentPageId } from '~/stores/page';
 import { useSelectedGrant } from '~/stores/ui';
 import { useSelectedGrant } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -42,7 +43,9 @@ export const SavePageControls = (props: SavePageControlsProps): JSX.Element | nu
   const { data: isAclEnabled } = useIsAclEnabled();
   const { data: isAclEnabled } = useIsAclEnabled();
   const { data: grantData, mutate: mutateGrant } = useSelectedGrant();
   const { data: grantData, mutate: mutateGrant } = useSelectedGrant();
   const { data: pageId } = useCurrentPageId();
   const { data: pageId } = useCurrentPageId();
+  const { data: _isWaitingSaveProcessing } = useWaitingSaveProcessing();
 
 
+  const isWaitingSaveProcessing = _isWaitingSaveProcessing === true; // ignore undefined
 
 
   const updateGrantHandler = useCallback((grantData: IPageGrantData): void => {
   const updateGrantHandler = useCallback((grantData: IPageGrantData): void => {
     mutateGrant(grantData);
     mutateGrant(grantData);
@@ -91,10 +94,19 @@ export const SavePageControls = (props: SavePageControlsProps): JSX.Element | nu
       }
       }
 
 
       <UncontrolledButtonDropdown direction="up">
       <UncontrolledButtonDropdown direction="up">
-        <Button data-testid="save-page-btn" id="caret" color="primary" className="btn-submit" onClick={save}>
+        <Button
+          id="caret" data-testid="save-page-btn"
+          color="primary"
+          className="btn-submit"
+          onClick={save}
+          disabled={isWaitingSaveProcessing}
+        >
+          { isWaitingSaveProcessing && (
+            <i className="fa fa-spinner fa-pulse mr-1"></i>
+          ) }
           {labelSubmitButton}
           {labelSubmitButton}
         </Button>
         </Button>
-        <DropdownToggle caret color="primary" />
+        <DropdownToggle caret color="primary" disabled={isWaitingSaveProcessing} />
         <DropdownMenu right>
         <DropdownMenu right>
           <DropdownItem onClick={saveAndOverwriteScopesOfDescendants}>
           <DropdownItem onClick={saveAndOverwriteScopesOfDescendants}>
             {labelOverwriteScopes}
             {labelOverwriteScopes}

+ 5 - 0
apps/app/src/stores/editor.tsx

@@ -17,6 +17,11 @@ import { useSWRxTagsInfo } from './page';
 import { useStaticSWR } from './use-static-swr';
 import { useStaticSWR } from './use-static-swr';
 
 
 
 
+export const useWaitingSaveProcessing = (): SWRResponse<boolean, Error> => {
+  return useStaticSWR('waitingSaveProcessing', undefined, { fallbackData: false });
+};
+
+
 export const useEditingMarkdown = (initialData?: string): SWRResponse<string, Error> => {
 export const useEditingMarkdown = (initialData?: string): SWRResponse<string, Error> => {
   return useStaticSWR('editingMarkdown', initialData);
   return useStaticSWR('editingMarkdown', initialData);
 };
 };