Просмотр исходного кода

Merge pull request #6713 from weseek/support/refactor-drawio-modal

support: refactor DrawioModal
Yuki Takei 3 лет назад
Родитель
Сommit
9337a9d881

+ 94 - 0
packages/app/src/components/PageEditor/DrawioCommunicationHelper.ts

@@ -0,0 +1,94 @@
+import loggerFactory from '~/utils/logger';
+
+
+const logger = loggerFactory('growi:cli:DrawioCommunicationHelper');
+
+export type DrawioConfig = {
+  css: string,
+  customFonts: string[],
+}
+
+export type DrawioCommunicationCallbackOptions = {
+  onClose?: () => void;
+  onSave?: (drawioData: string) => void;
+}
+
+export class DrawioCommunicationHelper {
+
+  drawioUri: string;
+
+  drawioConfig: DrawioConfig;
+
+  callbackOpts?: DrawioCommunicationCallbackOptions;
+
+
+  constructor(drawioUri: string, drawioConfig: DrawioConfig, callbackOpts?: DrawioCommunicationCallbackOptions) {
+    this.drawioUri = drawioUri;
+    this.drawioConfig = drawioConfig;
+    this.callbackOpts = callbackOpts;
+  }
+
+  onReceiveMessage(event: MessageEvent, drawioMxFile: string): void {
+
+    // check origin
+    if (event.origin != null && this.drawioUri != null) {
+      const originUrl = new URL(event.origin);
+      const drawioUrl = new URL(this.drawioUri);
+
+      if (originUrl.origin !== drawioUrl.origin) {
+        logger.debug(`Skipping the event because the origins are mismatched. expected: '${drawioUrl.origin}', actual: '${originUrl.origin}'`);
+        return;
+      }
+    }
+
+    if (event.data === 'ready') {
+      event.source?.postMessage(drawioMxFile, { targetOrigin: '*' });
+      return;
+    }
+
+    if (event.data === '{"event":"configure"}') {
+      if (event.source == null) {
+        return;
+      }
+
+      // refs:
+      //  * https://desk.draw.io/support/solutions/articles/16000103852-how-to-customise-the-draw-io-interface
+      //  * https://desk.draw.io/support/solutions/articles/16000042544-how-does-embed-mode-work-
+      //  * https://desk.draw.io/support/solutions/articles/16000058316-how-to-configure-draw-io-
+      event.source.postMessage(JSON.stringify({
+        action: 'configure',
+        config: this.drawioConfig,
+      }), { targetOrigin: '*' });
+
+      return;
+    }
+
+    if (typeof event.data === 'string' && event.data.match(/mxfile/)) {
+      if (event.data.length > 0) {
+        const parser = new DOMParser();
+        const dom = parser.parseFromString(event.data, 'text/xml');
+        const drawioData = dom.getElementsByTagName('diagram')[0].innerHTML;
+
+        /*
+        * Saving Drawio will be implemented by the following tasks
+        * https://redmine.weseek.co.jp/issues/100845
+        * https://redmine.weseek.co.jp/issues/104507
+        */
+
+        this.callbackOpts?.onSave?.(drawioData);
+      }
+
+      this.callbackOpts?.onClose?.();
+
+      return;
+    }
+
+    if (typeof event.data === 'string' && event.data.length === 0) {
+      this.callbackOpts?.onClose?.();
+      return;
+    }
+
+    // NOTHING DONE. (Receive unknown iframe message.)
+  }
+
+}

+ 72 - 18
packages/app/src/components/PageEditor/DrawioModal.tsx

@@ -1,4 +1,6 @@
 import React, {
+  useCallback,
+  useEffect,
   useMemo,
 } from 'react';
 
@@ -13,25 +15,42 @@ import { useDrawioUri } from '~/stores/context';
 import { useDrawioModal } from '~/stores/modal';
 import { usePersonalSettings } from '~/stores/personal-settings';
 
+import { DrawioCommunicationHelper } from './DrawioCommunicationHelper';
+
+
+const headerColor = '#334455';
+const fontFamily = "Lato, -apple-system, BlinkMacSystemFont, 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif";
+
+const drawioConfig = {
+  css: `
+  .geMenubarContainer { background-color: ${headerColor} !important; }
+  .geMenubar { background-color: ${headerColor} !important; }
+  .geEditor { font-family: ${fontFamily} !important; }
+  html td.mxPopupMenuItem {
+    font-family: ${fontFamily} !important;
+    font-size: 8pt !important;
+  }
+  `,
+  customFonts: ['Lato', 'Charter'],
+};
+
 
 type Props = {
   // onSave: (drawioData) => void,
 };
 
 export const DrawioModal = (props: Props): JSX.Element => {
-  const { data: growiDrawioUri } = useDrawioUri();
+  const { data: drawioUri } = useDrawioUri();
   const { data: personalSettingsInfo } = usePersonalSettings();
 
-
   const { data: drawioModalData, close: closeDrawioModal } = useDrawioModal();
   const isOpened = drawioModalData?.isOpened ?? false;
 
-  const cancel = () => {
-    closeDrawioModal();
-  };
+  const drawioUriWithParams = useMemo(() => {
+    if (drawioUri == null) {
+      return undefined;
+    }
 
-  const drawioUrl = useMemo(() => {
-    const drawioUri = growiDrawioUri || 'https://embed.diagrams.net/';
     const url = new URL(drawioUri);
 
     // refs: https://desk.draw.io/support/solutions/articles/16000042546-what-url-parameters-are-supported-
@@ -42,13 +61,46 @@ export const DrawioModal = (props: Props): JSX.Element => {
     url.searchParams.append('configure', '1');
 
     return url;
-  }, [growiDrawioUri, personalSettingsInfo?.lang]);
+  }, [drawioUri, personalSettingsInfo?.lang]);
+
+  const drawioCommunicationHelper = useMemo(() => {
+    if (drawioUri == null) {
+      return undefined;
+    }
 
+    return new DrawioCommunicationHelper(
+      drawioUri,
+      drawioConfig,
+      { onClose: closeDrawioModal },
+    );
+  }, [closeDrawioModal, drawioUri]);
+
+  const receiveMessageHandler = useCallback((event: MessageEvent) => {
+    if (drawioModalData == null) {
+      return;
+    }
+
+    drawioCommunicationHelper?.onReceiveMessage(event, drawioModalData.drawioMxFile);
+  }, [drawioCommunicationHelper, drawioModalData]);
+
+  useEffect(() => {
+    if (isOpened) {
+      window.addEventListener('message', receiveMessageHandler);
+    }
+    else {
+      window.removeEventListener('message', receiveMessageHandler);
+    }
+
+    // clean up
+    return function() {
+      window.removeEventListener('message', receiveMessageHandler);
+    };
+  }, [isOpened, receiveMessageHandler]);
 
   return (
     <Modal
       isOpen={isOpened}
-      toggle={cancel}
+      toggle={() => closeDrawioModal()}
       backdrop="static"
       className="drawio-modal grw-body-only-modal-expanded"
       size="xl"
@@ -62,15 +114,17 @@ export const DrawioModal = (props: Props): JSX.Element => {
           </div>
         </div>
         {/* iframe */}
-        <div className="w-100 h-100 position-absolute d-flex">
-          { isOpened && (
-            <iframe
-              src={drawioUrl.href}
-              className="border-0 flex-grow-1"
-            >
-            </iframe>
-          ) }
-        </div>
+        { drawioUriWithParams != null && (
+          <div className="w-100 h-100 position-absolute d-flex">
+            { isOpened && (
+              <iframe
+                src={drawioUriWithParams.href}
+                className="border-0 flex-grow-1"
+              >
+              </iframe>
+            ) }
+          </div>
+        ) }
       </ModalBody>
     </Modal>
   );

+ 2 - 2
packages/app/src/stores/context.tsx

@@ -130,8 +130,8 @@ export const useRevisionIdHackmdSynced = (initialData?: Nullable<any>): SWRRespo
   return useStaticSWR<Nullable<any>, Error>('revisionIdHackmdSynced', initialData);
 };
 
-export const useDrawioUri = (initialData?: Nullable<string>): SWRResponse<Nullable<string>, Error> => {
-  return useStaticSWR<Nullable<string>, Error>('drawioUri', initialData);
+export const useDrawioUri = (initialData?: string): SWRResponse<string, Error> => {
+  return useStaticSWR('drawioUri', initialData, { fallbackData: 'https://embed.diagrams.net/' });
 };
 
 export const useHackmdUri = (initialData?: Nullable<string>): SWRResponse<Nullable<string>, Error> => {

+ 0 - 60
packages/app/src/stores/modal.tsx

@@ -5,7 +5,6 @@ import {
   OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction, OnPutBackedFunction,
 } from '~/interfaces/ui';
 import { IUserGroupHasId } from '~/interfaces/user';
-import { dwawioConfig } from '~/utils/drawio-config';
 
 import { useStaticSWR } from './use-static-swr';
 
@@ -453,7 +452,6 @@ type DrawioModalStatus = {
 type DrawioModalStatusUtils = {
   open(drawioMxFile: string): void,
   close(): void,
-  receiveFromDrawio(event, drawioMxFile: string): void,
 }
 
 export const useDrawioModal = (status?: DrawioModalStatus): SWRResponse<DrawioModalStatus, Error> & DrawioModalStatusUtils => {
@@ -467,64 +465,7 @@ export const useDrawioModal = (status?: DrawioModalStatus): SWRResponse<DrawioMo
     swrResponse.mutate({ isOpened: false, drawioMxFile: '' });
   };
 
-  const receiveFromDrawio = (event, drawioMxFile: string) => {
-
-    if (event.data === 'ready') {
-      event.source.postMessage(drawioMxFile, '*');
-      return;
-    }
-
-    if (event.data === '{"event":"configure"}') {
-      if (event.source == null) {
-        return;
-      }
-
-      // refs:
-      //  * https://desk.draw.io/support/solutions/articles/16000103852-how-to-customise-the-draw-io-interface
-      //  * https://desk.draw.io/support/solutions/articles/16000042544-how-does-embed-mode-work-
-      //  * https://desk.draw.io/support/solutions/articles/16000058316-how-to-configure-draw-io-
-      event.source.postMessage(JSON.stringify({
-        action: 'configure',
-        config: dwawioConfig,
-      }), '*');
-
-      return;
-    }
-
-    if (typeof event.data === 'string' && event.data.match(/mxfile/)) {
-      if (event.data.length > 0) {
-        const parser = new DOMParser();
-        const dom = parser.parseFromString(event.data, 'text/xml');
-        const drawioData = dom.getElementsByTagName('diagram')[0].innerHTML;
-
-        /*
-        * Saving Drawio will be implemented by the following tasks
-        * https://redmine.weseek.co.jp/issues/100845
-        * https://redmine.weseek.co.jp/issues/104507
-        */
-
-        // if (props.onSave != null) {
-        //   props.onSave(drawioData);
-        // }
-      }
-
-      window.removeEventListener('message', () => receiveFromDrawio);
-      close();
-
-      return;
-    }
-
-    if (typeof event.data === 'string' && event.data.length === 0) {
-      close();
-
-      return;
-    }
-
-    // NOTHING DONE. (Receive unknown iframe message.)
-  };
-
   const open = (drawioMxFile: string): void => {
-    window.addEventListener('message', e => receiveFromDrawio(e, drawioMxFile));
     swrResponse.mutate({ isOpened: true, drawioMxFile });
   };
 
@@ -532,6 +473,5 @@ export const useDrawioModal = (status?: DrawioModalStatus): SWRResponse<DrawioMo
     ...swrResponse,
     open,
     close,
-    receiveFromDrawio,
   };
 };

+ 0 - 15
packages/app/src/utils/drawio-config.ts

@@ -1,15 +0,0 @@
-const headerColor = '#334455';
-const fontFamily = "Lato, -apple-system, BlinkMacSystemFont, 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif";
-
-export const dwawioConfig = {
-  css: `
-  .geMenubarContainer { background-color: ${headerColor} !important; }
-  .geMenubar { background-color: ${headerColor} !important; }
-  .geEditor { font-family: ${fontFamily} !important; }
-  html td.mxPopupMenuItem {
-    font-family: ${fontFamily} !important;
-    font-size: 8pt !important;
-  }
-  `,
-  customFonts: ['Lato', 'Charter'],
-};