DrawioModal.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import React, {
  2. useCallback,
  3. useEffect,
  4. useMemo,
  5. } from 'react';
  6. import { useCodeMirrorEditorIsolated } from '@growi/editor';
  7. import { useDrawioModalForEditor } from '@growi/editor/src/stores/use-drawio';
  8. import {
  9. Modal,
  10. ModalBody,
  11. } from 'reactstrap';
  12. import { getDiagramsNetLangCode } from '~/client/util/locale-utils';
  13. import { replaceFocusedDrawioWithEditor, getMarkdownDrawioMxfile } from '~/components/PageEditor/markdown-drawio-util-for-editor';
  14. import { useRendererConfig } from '~/stores/context';
  15. import { useDrawioModal } from '~/stores/modal';
  16. import { usePersonalSettings } from '~/stores/personal-settings';
  17. import loggerFactory from '~/utils/logger';
  18. import { LoadingSpinner } from '../LoadingSpinner';
  19. import { type DrawioConfig, DrawioCommunicationHelper } from './DrawioCommunicationHelper';
  20. const logger = loggerFactory('growi:components:DrawioModal');
  21. const headerColor = '#334455';
  22. const fontFamily = "-apple-system, BlinkMacSystemFont, 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif";
  23. const drawioConfig: DrawioConfig = {
  24. css: `
  25. .geMenubarContainer { background-color: ${headerColor} !important; }
  26. .geMenubar { background-color: ${headerColor} !important; }
  27. .geEditor { font-family: ${fontFamily} !important; }
  28. html td.mxPopupMenuItem {
  29. font-family: ${fontFamily} !important;
  30. font-size: 8pt !important;
  31. }
  32. `,
  33. customFonts: ['Charter'],
  34. compressXml: true,
  35. };
  36. export const DrawioModal = (): JSX.Element => {
  37. const { data: rendererConfig } = useRendererConfig();
  38. const { data: personalSettingsInfo } = usePersonalSettings({
  39. // make immutable
  40. revalidateIfStale: false,
  41. revalidateOnFocus: false,
  42. revalidateOnReconnect: false,
  43. });
  44. const { data: drawioModalData, close: closeDrawioModal } = useDrawioModal();
  45. const { data: drawioModalDataInEditor, close: closeDrawioModalInEditor } = useDrawioModalForEditor();
  46. const editorKey = drawioModalDataInEditor?.editorKey ?? null;
  47. const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(editorKey);
  48. const editor = codeMirrorEditor?.view;
  49. const isOpenedInEditor = (drawioModalDataInEditor?.isOpened ?? false) && (editor != null);
  50. const isOpened = drawioModalData?.isOpened ?? false;
  51. const drawioUriWithParams = useMemo(() => {
  52. if (rendererConfig == null) {
  53. return undefined;
  54. }
  55. let url;
  56. try {
  57. url = new URL(rendererConfig.drawioUri);
  58. }
  59. catch (err) {
  60. logger.debug(err);
  61. return undefined;
  62. }
  63. // refs: https://desk.draw.io/support/solutions/articles/16000042546-what-url-parameters-are-supported-
  64. url.searchParams.append('spin', '1');
  65. url.searchParams.append('embed', '1');
  66. url.searchParams.append('lang', getDiagramsNetLangCode(personalSettingsInfo?.lang || 'en'));
  67. url.searchParams.append('ui', 'atlas');
  68. url.searchParams.append('configure', '1');
  69. return url;
  70. }, [rendererConfig, personalSettingsInfo?.lang]);
  71. const drawioCommunicationHelper = useMemo(() => {
  72. if (rendererConfig == null) {
  73. return undefined;
  74. }
  75. const save = editor != null ? (drawioMxFile: string) => {
  76. replaceFocusedDrawioWithEditor(editor, drawioMxFile);
  77. } : drawioModalData?.onSave;
  78. return new DrawioCommunicationHelper(
  79. rendererConfig.drawioUri,
  80. drawioConfig,
  81. { onClose: isOpened ? closeDrawioModal : closeDrawioModalInEditor, onSave: save },
  82. );
  83. }, [closeDrawioModal, closeDrawioModalInEditor, drawioModalData?.onSave, editor, isOpened, rendererConfig]);
  84. const receiveMessageHandler = useCallback((event: MessageEvent) => {
  85. if (drawioModalData == null) {
  86. return;
  87. }
  88. const drawioMxFile = editor != null ? getMarkdownDrawioMxfile(editor) : drawioModalData.drawioMxFile;
  89. drawioCommunicationHelper?.onReceiveMessage(event, drawioMxFile);
  90. }, [drawioCommunicationHelper, drawioModalData, editor]);
  91. useEffect(() => {
  92. if (isOpened || isOpenedInEditor) {
  93. window.addEventListener('message', receiveMessageHandler);
  94. }
  95. else {
  96. window.removeEventListener('message', receiveMessageHandler);
  97. }
  98. // clean up
  99. return function() {
  100. window.removeEventListener('message', receiveMessageHandler);
  101. };
  102. }, [isOpened, isOpenedInEditor, receiveMessageHandler]);
  103. return (
  104. <Modal
  105. isOpen={isOpened || isOpenedInEditor}
  106. toggle={() => (isOpened ? closeDrawioModal() : closeDrawioModalInEditor())}
  107. backdrop="static"
  108. className="drawio-modal grw-body-only-modal-expanded"
  109. size="xl"
  110. keyboard={false}
  111. >
  112. <ModalBody className="p-0">
  113. {/* Loading spinner */}
  114. <div className="w-100 h-100 position-absolute d-flex">
  115. <div className="mx-auto my-auto">
  116. <LoadingSpinner className="mx-auto text-muted fs-2" />
  117. </div>
  118. </div>
  119. {/* iframe */}
  120. { drawioUriWithParams != null && (
  121. <div className="w-100 h-100 position-absolute d-flex">
  122. { (isOpened || isOpenedInEditor) && (
  123. <iframe
  124. src={drawioUriWithParams.href}
  125. className="border-0 flex-grow-1"
  126. >
  127. </iframe>
  128. ) }
  129. </div>
  130. ) }
  131. </ModalBody>
  132. </Modal>
  133. );
  134. };