Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

imprv: Show a spinner into the save button while the saving process #7579

Merged
merged 2 commits into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions apps/app/src/components/PageEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
useIsEnabledUnsavedWarning,
useIsConflict,
useEditingMarkdown,
useWaitingSaveProcessing,
} from '~/stores/editor';
import { useConflictDiffModal } from '~/stores/modal';
import {
Expand Down Expand Up @@ -76,7 +77,7 @@ const PageEditor = React.memo((): JSX.Element => {
const { t } = useTranslation();
const router = useRouter();

const { data: isNotFound, mutate: mutateIsNotFound } = useIsNotFound();
const { data: isNotFound } = useIsNotFound();
const { data: pageId, mutate: mutateCurrentPageId } = useCurrentPageId();
const { data: currentPagePath } = useCurrentPagePath();
const { data: currentPathname } = useCurrentPathname();
Expand All @@ -89,6 +90,7 @@ const PageEditor = React.memo((): JSX.Element => {
const { data: isEnabledAttachTitleHeader } = useIsEnabledAttachTitleHeader();
const { data: templateBodyData } = useTemplateBodyData();
const { data: isEditable } = useIsEditable();
const { mutate: mutateWaitingSaveProcessing } = useWaitingSaveProcessing();
const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
const { data: isSlackEnabled } = useIsSlackEnabled();
const { data: isTextlintEnabled } = useIsTextlintEnabled();
Expand Down Expand Up @@ -201,6 +203,8 @@ const PageEditor = React.memo((): JSX.Element => {
const options = Object.assign(optionsToSave, opts);

try {
mutateWaitingSaveProcessing(true);

const { page } = await saveOrUpdate(
markdownToSave.current,
{ pageId, path: currentPagePath || currentPathname, revisionId: currentRevisionId },
Expand All @@ -223,10 +227,14 @@ const PageEditor = React.memo((): JSX.Element => {
}
return null;
}
finally {
mutateWaitingSaveProcessing(false);
}

}, [
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}) => {
Expand Down Expand Up @@ -331,7 +339,7 @@ const PageEditor = React.memo((): JSX.Element => {
finally {
editorRef.current.terminateUploadingState();
}
}, [currentPagePath, mutateCurrentPage, mutateCurrentPageId, mutateGrant, mutateIsLatestRevision, mutateIsNotFound, pageId]);
}, [currentPagePath, mutateCurrentPage, mutateCurrentPageId, mutateGrant, mutateIsLatestRevision, pageId]);


const scrollPreviewByEditorLine = useCallback((line: number) => {
Expand Down
27 changes: 24 additions & 3 deletions apps/app/src/components/PageEditorByHackmd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
useCurrentPathname, useHackmdUri,
} from '~/stores/context';
import {
useIsSlackEnabled, usePageTagsForEditors, useIsEnabledUnsavedWarning,
useIsSlackEnabled, usePageTagsForEditors, useIsEnabledUnsavedWarning, useWaitingSaveProcessing,
} from '~/stores/editor';
import {
usePageIdOnHackmd, useHasDraftOnHackmd, useRevisionIdHackmdSynced, useIsHackmdDraftUpdatingInRealtime,
Expand Down Expand Up @@ -56,6 +56,7 @@ export const PageEditorByHackmd = (): JSX.Element => {
const router = useRouter();

const { data: isNotFound } = useIsNotFound();
const { mutate: mutateWaitingSaveProcessing } = useWaitingSaveProcessing();
const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
const { data: currentPagePath } = useCurrentPagePath();
const { data: currentPathname } = useCurrentPathname();
Expand Down Expand Up @@ -116,6 +117,8 @@ export const PageEditorByHackmd = (): JSX.Element => {
throw new Error('Some materials to save are invalid');
}

mutateWaitingSaveProcessing(true);

const options = Object.assign(optionsToSave, opts, { isSyncRevisionToHackmd: true });

const markdown = await hackmdEditorRef.current.getValue();
Expand All @@ -142,8 +145,16 @@ export const PageEditorByHackmd = (): JSX.Element => {
logger.error('failed to save', error);
toastError(error.message);
}
finally {
mutateWaitingSaveProcessing(false);
}

// 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
useEffect(() => {
Expand Down Expand Up @@ -249,6 +260,8 @@ export const PageEditorByHackmd = (): JSX.Element => {
*/
const onSaveWithShortcut = useCallback(async(markdown) => {
try {
mutateWaitingSaveProcessing(true);

const currentPagePathOrPathname = currentPagePath || currentPathname;
if (
pageId == null || revisionIdHackmdSynced == null || currentPagePathOrPathname == null || optionsToSave == null
Expand Down Expand Up @@ -278,8 +291,16 @@ export const PageEditorByHackmd = (): JSX.Element => {
logger.error('failed to save', error);
toastError(error.message);
}
finally {
mutateWaitingSaveProcessing(false);
}

// 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
Expand Down
16 changes: 14 additions & 2 deletions apps/app/src/components/SavePageControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { IPageGrantData } from '~/interfaces/page';
import {
useIsEditable, useIsAclEnabled,
} from '~/stores/context';
import { useWaitingSaveProcessing } from '~/stores/editor';
import { useCurrentPagePath, useCurrentPageId } from '~/stores/page';
import { useSelectedGrant } from '~/stores/ui';
import loggerFactory from '~/utils/logger';
Expand Down Expand Up @@ -42,7 +43,9 @@ export const SavePageControls = (props: SavePageControlsProps): JSX.Element | nu
const { data: isAclEnabled } = useIsAclEnabled();
const { data: grantData, mutate: mutateGrant } = useSelectedGrant();
const { data: pageId } = useCurrentPageId();
const { data: _isWaitingSaveProcessing } = useWaitingSaveProcessing();

const isWaitingSaveProcessing = _isWaitingSaveProcessing === true; // ignore undefined

const updateGrantHandler = useCallback((grantData: IPageGrantData): void => {
mutateGrant(grantData);
Expand Down Expand Up @@ -91,10 +94,19 @@ export const SavePageControls = (props: SavePageControlsProps): JSX.Element | nu
}

<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}
</Button>
<DropdownToggle caret color="primary" />
<DropdownToggle caret color="primary" disabled={isWaitingSaveProcessing} />
<DropdownMenu right>
<DropdownItem onClick={saveAndOverwriteScopesOfDescendants}>
{labelOverwriteScopes}
Expand Down
5 changes: 5 additions & 0 deletions apps/app/src/stores/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ import { useSWRxTagsInfo } from './page';
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> => {
return useStaticSWR('editingMarkdown', initialData);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,16 @@ context('Modal for page operation', () => {
cy.screenshot(`${ssPrefix}today-add-page-name`);
cy.getByTestid('btn-create-memo').click();
});
cy.getByTestid('page-editor').should('be.visible');

cy.getByTestid('page-editor').should('be.visible');
cy.getByTestid('save-page-btn').as('save-page-btn').should('be.visible');
cy.waitUntil(() => {
// do
cy.getByTestid('save-page-btn').should('be.visible').click();
cy.get('@save-page-btn').click();
// wait until
return cy.get('.layout-root').then($elem => $elem.hasClass('editing'));
return cy.get('@save-page-btn').then($elem => $elem.is(':disabled'));
});

cy.getByTestid('grw-contextual-sub-nav').should('be.visible');
cy.get('.layout-root').should('not.have.class', 'editing');

cy.collapseSidebar(true);
cy.waitUntilSkeletonDisappear();
Expand All @@ -80,8 +80,15 @@ context('Modal for page operation', () => {
cy.screenshot(`${ssPrefix}under-path-add-page-name`);
cy.getByTestid('btn-create-page-under-below').click();
});

cy.getByTestid('page-editor').should('be.visible');
cy.getByTestid('save-page-btn').click();
cy.getByTestid('save-page-btn').as('save-page-btn').should('be.visible');
cy.waitUntil(() => {
// do
cy.get('@save-page-btn').click();
// wait until
return cy.get('@save-page-btn').then($elem => $elem.is(':disabled'));
});
cy.get('.layout-root').should('not.have.class', 'editing');

cy.getByTestid('grw-contextual-sub-nav').should('be.visible');
Expand Down