PagePresentationModal.tsx 3.2 KB

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