DrawioModal.jsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import i18next from 'i18next';
  4. import {
  5. Modal,
  6. ModalBody,
  7. } from 'reactstrap';
  8. import { withUnstatedContainers } from '../UnstatedUtils';
  9. import AppContainer from '~/client/services/AppContainer';
  10. import EditorContainer from '~/client/services/EditorContainer';
  11. import { getDiagramsNetLangCode } from '~/client/util/locale-utils';
  12. class DrawioModal extends React.PureComponent {
  13. constructor(props) {
  14. super(props);
  15. this.state = {
  16. show: false,
  17. drawioMxFile: '',
  18. };
  19. this.headerColor = '#334455';
  20. this.fontFamily = "Lato, -apple-system, BlinkMacSystemFont, 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif";
  21. this.init = this.init.bind(this);
  22. this.cancel = this.cancel.bind(this);
  23. this.receiveFromDrawio = this.receiveFromDrawio.bind(this);
  24. }
  25. init(drawioMxFile) {
  26. const initDrawioMxFile = drawioMxFile;
  27. this.setState(
  28. {
  29. drawioMxFile: initDrawioMxFile,
  30. },
  31. );
  32. }
  33. show(drawioMxFile) {
  34. this.init(drawioMxFile);
  35. window.addEventListener('message', this.receiveFromDrawio);
  36. this.setState({ show: true });
  37. }
  38. hide() {
  39. this.setState({
  40. show: false,
  41. });
  42. }
  43. cancel() {
  44. this.hide();
  45. }
  46. receiveFromDrawio(event) {
  47. if (event.data === 'ready') {
  48. event.source.postMessage(this.state.drawioMxFile, '*');
  49. return;
  50. }
  51. if (event.data === '{"event":"configure"}') {
  52. if (event.source == null) {
  53. return;
  54. }
  55. // refs:
  56. // * https://desk.draw.io/support/solutions/articles/16000103852-how-to-customise-the-draw-io-interface
  57. // * https://desk.draw.io/support/solutions/articles/16000042544-how-does-embed-mode-work-
  58. // * https://desk.draw.io/support/solutions/articles/16000058316-how-to-configure-draw-io-
  59. event.source.postMessage(JSON.stringify({
  60. action: 'configure',
  61. config: {
  62. css: `
  63. .geMenubarContainer { background-color: ${this.headerColor} !important; }
  64. .geMenubar { background-color: ${this.headerColor} !important; }
  65. .geEditor { font-family: ${this.fontFamily} !important; }
  66. html td.mxPopupMenuItem {
  67. font-family: ${this.fontFamily} !important;
  68. font-size: 8pt !important;
  69. }
  70. `,
  71. customFonts: ['Lato', 'Charter'],
  72. },
  73. }), '*');
  74. return;
  75. }
  76. if (typeof event.data === 'string' && event.data.match(/mxfile/)) {
  77. if (event.data.length > 0) {
  78. const parser = new DOMParser();
  79. const dom = parser.parseFromString(event.data, 'text/xml');
  80. const value = dom.getElementsByTagName('diagram')[0].innerHTML;
  81. if (this.props.onSave != null) {
  82. this.props.onSave(value);
  83. }
  84. }
  85. window.removeEventListener('message', this.receiveFromDrawio);
  86. this.hide();
  87. return;
  88. }
  89. if (typeof event.data === 'string' && event.data.length === 0) {
  90. window.removeEventListener('message', this.receiveFromDrawio);
  91. this.hide();
  92. return;
  93. }
  94. // NOTHING DONE. (Receive unknown iframe message.)
  95. }
  96. get drawioURL() {
  97. const { config } = this.props.appContainer;
  98. const drawioUri = config.env.DRAWIO_URI || 'https://embed.diagrams.net/';
  99. const url = new URL(drawioUri);
  100. // refs: https://desk.draw.io/support/solutions/articles/16000042546-what-url-parameters-are-supported-
  101. url.searchParams.append('spin', 1);
  102. url.searchParams.append('embed', 1);
  103. url.searchParams.append('lang', getDiagramsNetLangCode(i18next.language));
  104. url.searchParams.append('ui', 'atlas');
  105. url.searchParams.append('configure', 1);
  106. return url;
  107. }
  108. render() {
  109. return (
  110. <Modal
  111. isOpen={this.state.show}
  112. toggle={this.cancel}
  113. backdrop="static"
  114. className="drawio-modal"
  115. size="xl"
  116. keyboard={false}
  117. >
  118. <ModalBody className="p-0">
  119. {/* Loading spinner */}
  120. <div className="w-100 h-100 position-absolute d-flex">
  121. <div className="mx-auto my-auto">
  122. <i className="fa fa-3x fa-spinner fa-pulse mx-auto text-muted"></i>
  123. </div>
  124. </div>
  125. {/* iframe */}
  126. <div className="w-100 h-100 position-absolute d-flex">
  127. { this.state.show && (
  128. <iframe
  129. src={this.drawioURL}
  130. className="border-0 flex-grow-1"
  131. >
  132. </iframe>
  133. ) }
  134. </div>
  135. </ModalBody>
  136. </Modal>
  137. );
  138. }
  139. }
  140. DrawioModal.propTypes = {
  141. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  142. editorContainer: PropTypes.instanceOf(EditorContainer).isRequired,
  143. onSave: PropTypes.func,
  144. };
  145. export default withUnstatedContainers(DrawioModal, [AppContainer, EditorContainer]);