PagePresentationModal.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import React, { useCallback, type JSX } from 'react';
  2. import type { PresentationProps } from '@growi/presentation/dist/client';
  3. import { useSlidesByFrontmatter } from '@growi/presentation/dist/services';
  4. import { LoadingSpinner } from '@growi/ui/dist/components';
  5. import { useFullScreen } from '@growi/ui/dist/utils';
  6. import dynamic from 'next/dynamic';
  7. import type { Options as ReactMarkdownOptions } from 'react-markdown';
  8. import {
  9. Modal, ModalBody,
  10. } from 'reactstrap';
  11. import { useCurrentPageData } from '~/states/page';
  12. import { useRendererConfig } from '~/states/server-configurations';
  13. import { usePresentationModalActions, usePresentationModalStatus } from '~/states/ui/modal/page-presentation';
  14. import { useNextThemes } from '~/stores-universal/use-next-themes';
  15. import { usePresentationViewOptions } from '~/stores/renderer';
  16. import { RendererErrorMessage } from './Common/RendererErrorMessage';
  17. import styles from './PagePresentationModal.module.scss';
  18. const moduleClass = styles['grw-presentation-modal'] ?? '';
  19. const Presentation = dynamic<PresentationProps>(() => import('./Presentation/Presentation').then(mod => mod.Presentation), {
  20. ssr: false,
  21. loading: () => (
  22. <LoadingSpinner className="text-muted fs-1" />
  23. ),
  24. });
  25. const PagePresentationModal = (): JSX.Element => {
  26. const presentationModalData = usePresentationModalStatus();
  27. const { close: closePresentationModal } = usePresentationModalActions();
  28. const { isDarkMode } = useNextThemes();
  29. const fullscreen = useFullScreen();
  30. const currentPage = useCurrentPageData();
  31. const { data: rendererOptions, isLoading } = usePresentationViewOptions();
  32. const { isEnabledMarp } = useRendererConfig();
  33. const markdown = currentPage?.revision?.body;
  34. const isSlide = useSlidesByFrontmatter(markdown, isEnabledMarp);
  35. const toggleFullscreenHandler = useCallback(() => {
  36. if (fullscreen.active) {
  37. fullscreen.exit();
  38. }
  39. else {
  40. fullscreen.enter();
  41. }
  42. }, [fullscreen]);
  43. const closeHandler = useCallback(() => {
  44. if (fullscreen.active) {
  45. fullscreen.exit();
  46. }
  47. closePresentationModal();
  48. }, [fullscreen, closePresentationModal]);
  49. const isOpen = presentationModalData?.isOpened ?? false;
  50. if (!isOpen) {
  51. return <></>;
  52. }
  53. return (
  54. <Modal
  55. isOpen={isOpen}
  56. toggle={closeHandler}
  57. data-testid="page-presentation-modal"
  58. className={moduleClass}
  59. >
  60. <div className="grw-presentation-controls d-flex">
  61. <button
  62. className="btn material-symbols-outlined"
  63. type="button"
  64. aria-label="fullscreen"
  65. onClick={toggleFullscreenHandler}
  66. >
  67. {fullscreen.active ? 'close_fullscreen' : 'open_in_full'}
  68. </button>
  69. <button className="btn-close" type="button" aria-label="Close" onClick={closeHandler}></button>
  70. </div>
  71. <ModalBody className="modal-body d-flex justify-content-center align-items-center">
  72. { !isLoading && rendererOptions == null && <RendererErrorMessage />}
  73. { rendererOptions != null && isEnabledMarp != null && (
  74. <Presentation
  75. options={{
  76. rendererOptions: rendererOptions as ReactMarkdownOptions,
  77. revealOptions: {
  78. embedded: true,
  79. hash: true,
  80. },
  81. isDarkMode,
  82. }}
  83. marp={isSlide?.marp}
  84. >
  85. {markdown}
  86. </Presentation>
  87. ) }
  88. </ModalBody>
  89. </Modal>
  90. );
  91. };
  92. export default PagePresentationModal;