DisplaySwitcher.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import React, { useCallback, useEffect, useMemo } from 'react';
  2. import { pagePathUtils } from '@growi/core';
  3. import dynamic from 'next/dynamic';
  4. import { SocketEventName } from '~/interfaces/websocket';
  5. import {
  6. useIsEditable, useShareLinkId, useIsNotFound,
  7. } from '~/stores/context';
  8. import { useIsHackmdDraftUpdatingInRealtime } from '~/stores/hackmd';
  9. import { useCurrentPagePath, useSWRxCurrentPage } from '~/stores/page';
  10. import {
  11. useSetRemoteLatestPageData,
  12. } from '~/stores/remote-latest-page';
  13. import { EditorMode, useEditorMode } from '~/stores/ui';
  14. import { useGlobalSocket } from '~/stores/websocket';
  15. import CustomTabContent from '../CustomNavigation/CustomTabContent';
  16. import { Page } from '../Page';
  17. import { UserInfoProps } from '../User/UserInfo';
  18. const { isUsersHomePage } = pagePathUtils;
  19. const PageEditor = dynamic(() => import('../PageEditor'), { ssr: false });
  20. const PageEditorByHackmd = dynamic(() => import('../PageEditorByHackmd').then(mod => mod.PageEditorByHackmd), { ssr: false });
  21. const EditorNavbarBottom = dynamic(() => import('../PageEditor/EditorNavbarBottom'), { ssr: false });
  22. const HashChanged = dynamic(() => import('../EventListeneres/HashChanged'), { ssr: false });
  23. const NotFoundPage = dynamic(() => import('../NotFoundPage'), { ssr: false });
  24. const UserInfo = dynamic<UserInfoProps>(() => import('../User/UserInfo').then(mod => mod.UserInfo), { ssr: false });
  25. const PageView = React.memo((): JSX.Element => {
  26. const { data: currentPagePath } = useCurrentPagePath();
  27. const { data: shareLinkId } = useShareLinkId();
  28. const { data: isNotFound } = useIsNotFound();
  29. const { data: currentPage } = useSWRxCurrentPage();
  30. const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
  31. const { mutate: mutateIsHackmdDraftUpdatingInRealtime } = useIsHackmdDraftUpdatingInRealtime();
  32. const isUsersHomePagePath = isUsersHomePage(currentPagePath ?? '');
  33. const { data: socket } = useGlobalSocket();
  34. const setLatestRemotePageData = useCallback((data) => {
  35. const { s2cMessagePageUpdated } = data;
  36. const remoteData = {
  37. remoteRevisionId: s2cMessagePageUpdated.revisionId,
  38. remoteRevisionBody: s2cMessagePageUpdated.revisionBody,
  39. remoteRevisionLastUpdateUser: s2cMessagePageUpdated.remoteLastUpdateUser,
  40. remoteRevisionLastUpdatedAt: s2cMessagePageUpdated.revisionUpdateAt,
  41. revisionIdHackmdSynced: s2cMessagePageUpdated.revisionIdHackmdSynced,
  42. hasDraftOnHackmd: s2cMessagePageUpdated.hasDraftOnHackmd,
  43. };
  44. setRemoteLatestPageData(remoteData);
  45. }, [setRemoteLatestPageData]);
  46. const setIsHackmdDraftUpdatingInRealtime = useCallback((data) => {
  47. const { s2cMessagePageUpdated } = data;
  48. if (s2cMessagePageUpdated.pageId === currentPage?._id) {
  49. mutateIsHackmdDraftUpdatingInRealtime(true);
  50. }
  51. }, [currentPage?._id, mutateIsHackmdDraftUpdatingInRealtime]);
  52. // listen socket for someone updating this page
  53. useEffect(() => {
  54. if (socket == null) { return }
  55. socket.on(SocketEventName.PageUpdated, setLatestRemotePageData);
  56. return () => {
  57. socket.off(SocketEventName.PageUpdated, setLatestRemotePageData);
  58. };
  59. }, [setLatestRemotePageData, socket]);
  60. // listen socket for hackmd saved
  61. useEffect(() => {
  62. if (socket == null) { return }
  63. socket.on(SocketEventName.EditingWithHackmd, setIsHackmdDraftUpdatingInRealtime);
  64. return () => {
  65. socket.off(SocketEventName.EditingWithHackmd, setIsHackmdDraftUpdatingInRealtime);
  66. };
  67. }, [setIsHackmdDraftUpdatingInRealtime, socket]);
  68. return (
  69. <>
  70. { isUsersHomePagePath && <UserInfo author={currentPage?.creator} /> }
  71. { !isNotFound && <Page currentPage={currentPage ?? undefined} /> }
  72. { isNotFound && <NotFoundPage /> }
  73. </>
  74. );
  75. });
  76. PageView.displayName = 'PageView';
  77. const DisplaySwitcher = React.memo((): JSX.Element => {
  78. const { data: isEditable } = useIsEditable();
  79. const { data: editorMode = EditorMode.View } = useEditorMode();
  80. const isViewMode = editorMode === EditorMode.View;
  81. const navTabMapping = useMemo(() => {
  82. return {
  83. [EditorMode.View]: {
  84. Content: () => (
  85. <div data-testid="page-view" id="page-view">
  86. <PageView />
  87. </div>
  88. ),
  89. },
  90. [EditorMode.Editor]: {
  91. Content: () => (
  92. isEditable
  93. ? (
  94. <div data-testid="page-editor" id="page-editor">
  95. <PageEditor />
  96. </div>
  97. )
  98. : <></>
  99. ),
  100. },
  101. [EditorMode.HackMD]: {
  102. Content: () => (
  103. isEditable
  104. ? (
  105. <div id="page-editor-with-hackmd">
  106. <PageEditorByHackmd />
  107. </div>
  108. )
  109. : <></>
  110. ),
  111. },
  112. };
  113. }, [isEditable]);
  114. return (
  115. <>
  116. <CustomTabContent activeTab={editorMode} navTabMapping={navTabMapping} />
  117. { isEditable && !isViewMode && <EditorNavbarBottom /> }
  118. { isEditable && <HashChanged></HashChanged> }
  119. </>
  120. );
  121. });
  122. DisplaySwitcher.displayName = 'DisplaySwitcher';
  123. export default DisplaySwitcher;