PagePresentationModal.tsx 3.2 KB

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