DrawioModal.tsx 5.4 KB

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