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

impl updating drawio diagram from Page component

Yuki Takei 3 лет назад
Родитель
Сommit
a5d8633e4f

+ 78 - 153
packages/app/src/components/Page.tsx

@@ -5,29 +5,29 @@ import React, {
 
 
 import EventEmitter from 'events';
 import EventEmitter from 'events';
 
 
+import { DrawioEditByViewerProps } from '@growi/remark-drawio-plugin';
+import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
 // import { debounce } from 'throttle-debounce';
 // import { debounce } from 'throttle-debounce';
 
 
 import { HtmlElementNode } from 'rehype-toc';
 import { HtmlElementNode } from 'rehype-toc';
 
 
+import { useSaveOrUpdate } from '~/client/services/page-operation';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
-import { getOptionsToSave } from '~/client/util/editor';
+import { OptionsToSave } from '~/interfaces/page-operation';
 import {
 import {
   useIsGuestUser, useShareLinkId,
   useIsGuestUser, useShareLinkId,
 } from '~/stores/context';
 } from '~/stores/context';
-import {
-  useSWRxSlackChannels, useIsSlackEnabled, usePageTagsForEditors, useIsEnabledUnsavedWarning,
-} from '~/stores/editor';
-import { useSWRxCurrentPage } from '~/stores/page';
+import { useDrawioModal } from '~/stores/modal';
+import { useSWRxCurrentPage, useSWRxTagsInfo } from '~/stores/page';
 import { useViewOptions } from '~/stores/renderer';
 import { useViewOptions } from '~/stores/renderer';
 import {
 import {
   useCurrentPageTocNode,
   useCurrentPageTocNode,
-  useEditorMode, useIsMobile,
+  useIsMobile,
 } from '~/stores/ui';
 } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import RevisionRenderer from './Page/RevisionRenderer';
 import RevisionRenderer from './Page/RevisionRenderer';
-import { DrawioModal } from './PageEditor/DrawioModal';
 import mdu from './PageEditor/MarkdownDrawioUtil';
 import mdu from './PageEditor/MarkdownDrawioUtil';
 
 
 
 
@@ -39,118 +39,9 @@ const LinkEditModal = dynamic(() => import('./PageEditor/LinkEditModal'), { ssr:
 
 
 const logger = loggerFactory('growi:Page');
 const logger = loggerFactory('growi:Page');
 
 
-type PageSubstanceProps = {
-  rendererOptions: any,
-  page: any,
-  pageTags?: string[],
-  editorMode: string,
-  isGuestUser: boolean,
-  isMobile?: boolean,
-  isSlackEnabled: boolean,
-  slackChannels: string,
-};
-
-class PageSubstance extends React.Component<PageSubstanceProps> {
-
-  gridEditModal: any;
-
-  linkEditModal: any;
-
-  drawioModal: any;
-
-  constructor(props: PageSubstanceProps) {
-    super(props);
-
-    this.state = {
-      currentTargetTableArea: null,
-      currentTargetDrawioArea: null,
-    };
-
-    this.gridEditModal = React.createRef();
-    this.linkEditModal = React.createRef();
-    this.drawioModal = React.createRef();
-
-    this.saveHandlerForDrawioModal = this.saveHandlerForDrawioModal.bind(this);
-  }
-
-  /**
-   * launch DrawioModal with data specified by arguments
-   * @param beginLineNumber
-   * @param endLineNumber
-   */
-  launchDrawioModal(beginLineNumber, endLineNumber) {
-    // const markdown = this.props.pageContainer.state.markdown;
-    // const drawioMarkdownArray = markdown.split(/\r\n|\r|\n/).slice(beginLineNumber - 1, endLineNumber);
-    // const drawioData = drawioMarkdownArray.slice(1, drawioMarkdownArray.length - 1).join('\n').trim();
-    // this.setState({ currentTargetDrawioArea: { beginLineNumber, endLineNumber } });
-    // this.drawioModal.current.show(drawioData);
-  }
-
-  async saveHandlerForDrawioModal(drawioData) {
-  //   const {
-  //     isSlackEnabled, slackChannels, pageContainer, pageTags, grant, grantGroupId, grantGroupName, mutateIsEnabledUnsavedWarning,
-  //   } = this.props;
-  //   const optionsToSave = getOptionsToSave(isSlackEnabled, slackChannels, grant, grantGroupId, grantGroupName, pageTags);
-
-    //   const newMarkdown = mdu.replaceDrawioInMarkdown(
-    //     drawioData,
-    //     this.props.pageContainer.state.markdown,
-    //     this.state.currentTargetDrawioArea.beginLineNumber,
-    //     this.state.currentTargetDrawioArea.endLineNumber,
-    //   );
-
-    //   try {
-    //     // disable unsaved warning
-    //     mutateIsEnabledUnsavedWarning(false);
-
-    //     // eslint-disable-next-line no-unused-vars
-    //     const { page, tags } = await pageContainer.save(newMarkdown, this.props.editorMode, optionsToSave);
-    //     logger.debug('success to save');
-
-    // // Todo: add translation
-    //   toastSuccess(t(''));
-    //   }
-  //   catch (error) {
-  //     logger.error('failed to save', error);
-  //     toastError(error);
-  //   }
-  //   finally {
-  //     this.setState({ currentTargetDrawioArea: null });
-  //   }
-  }
-
-  override render() {
-    const {
-      rendererOptions, page, isMobile, isGuestUser,
-    } = this.props;
-    const { path } = page;
-    const { _id: revisionId, body: markdown } = page.revision;
-
-    return (
-      <div className={`mb-5 ${isMobile ? 'page-mobile' : ''}`}>
-
-        { revisionId != null && (
-          <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} />
-        )}
-
-        { !isGuestUser && (
-          <>
-            <GridEditModal ref={this.gridEditModal} />
-            <LinkEditModal ref={this.linkEditModal} />
-            {/* TODO: use global DrawioModal https://redmine.weseek.co.jp/issues/105981 */}
-            {/* <DrawioModal
-              ref={this.drawioModal}
-              onSave={this.saveHandlerForDrawioModal}
-            /> */}
-          </>
-        )}
-      </div>
-    );
-  }
-
-}
 
 
 export const Page = (props) => {
 export const Page = (props) => {
+  const { t } = useTranslation();
   // Pass tocRef to generateViewOptions (=> rehypePlugin => customizeTOC) to call mutateCurrentPageTocNode when tocRef.current changes.
   // Pass tocRef to generateViewOptions (=> rehypePlugin => customizeTOC) to call mutateCurrentPageTocNode when tocRef.current changes.
   // The toc node passed by customizeTOC is assigned to tocRef.current.
   // The toc node passed by customizeTOC is assigned to tocRef.current.
   const tocRef = useRef<HtmlElementNode>();
   const tocRef = useRef<HtmlElementNode>();
@@ -160,41 +51,72 @@ export const Page = (props) => {
   }, []);
   }, []);
 
 
   const { data: shareLinkId } = useShareLinkId();
   const { data: shareLinkId } = useShareLinkId();
-  const { data: currentPage } = useSWRxCurrentPage(shareLinkId ?? undefined);
-  const { data: editorMode } = useEditorMode();
+  const { data: currentPage, mutate: mutateCurrentPage } = useSWRxCurrentPage(shareLinkId ?? undefined);
+  const { data: tagsInfo } = useSWRxTagsInfo(currentPage?._id);
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isMobile } = useIsMobile();
   const { data: isMobile } = useIsMobile();
-  const { data: slackChannelsData } = useSWRxSlackChannels(currentPage?.path);
-  const { data: isSlackEnabled } = useIsSlackEnabled();
-  const { data: pageTags } = usePageTagsForEditors(null); // TODO: pass pageId
   const { data: rendererOptions } = useViewOptions(storeTocNodeHandler);
   const { data: rendererOptions } = useViewOptions(storeTocNodeHandler);
-  const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
   const { mutate: mutateCurrentPageTocNode } = useCurrentPageTocNode();
   const { mutate: mutateCurrentPageTocNode } = useCurrentPageTocNode();
+  const { open: openDrawioModal } = useDrawioModal();
+
+  const saveOrUpdate = useSaveOrUpdate();
+
+  const saveByDrawioModal = useCallback(async(drawioMxFile: string, bol: number, eol: number) => {
+    if (currentPage == null || tagsInfo == null) {
+      return;
+    }
+
+    const currentMarkdown = currentPage.revision.body;
+    const optionsToSave: OptionsToSave = {
+      isSlackEnabled: false,
+      slackChannels: '',
+      grant: currentPage.grant,
+      grantUserGroupId: currentPage.grantedGroup?._id,
+      grantUserGroupName: currentPage.grantedGroup?.name,
+      pageTags: tagsInfo.tags,
+    };
+
+    const newMarkdown = mdu.replaceDrawioInMarkdown(drawioMxFile, currentMarkdown, bol, eol);
+
+    try {
+      const currentRevisionId = currentPage.revision._id;
+      await saveOrUpdate(
+        newMarkdown,
+        { pageId: currentPage._id, path: currentPage.path, revisionId: currentRevisionId },
+        optionsToSave,
+      );
+
+      toastSuccess(t('toaster.save_succeeded'));
 
 
-  const pageRef = useRef(null);
+      // rerender
+      mutateCurrentPage();
+    }
+    catch (error) {
+      logger.error('failed to save', error);
+      toastError(error);
+    }
+  }, [currentPage, mutateCurrentPage, saveOrUpdate, t, tagsInfo]);
 
 
   useEffect(() => {
   useEffect(() => {
     mutateCurrentPageTocNode(tocRef.current);
     mutateCurrentPageTocNode(tocRef.current);
   // eslint-disable-next-line react-hooks/exhaustive-deps
   // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [mutateCurrentPageTocNode, tocRef.current]); // include tocRef.current to call mutateCurrentPageTocNode when tocRef.current changes
   }, [mutateCurrentPageTocNode, tocRef.current]); // include tocRef.current to call mutateCurrentPageTocNode when tocRef.current changes
 
 
-  // // set handler to open DrawioModal
-  // useEffect(() => {
-  //   const handler = (beginLineNumber, endLineNumber) => {
-  //     if (pageRef?.current != null) {
-  //       pageRef.current.launchDrawioModal(beginLineNumber, endLineNumber);
-  //     }
-  //   };
-  //   window.globalEmitter.on('launchDrawioModal', handler);
-
-  //   return function cleanup() {
-  //     window.globalEmitter.removeListener('launchDrawioModal', handler);
-  //   };
-  // }, []);
-
-  if (currentPage == null || editorMode == null || isGuestUser == null || rendererOptions == null) {
+  // set handler to open DrawioModal
+  useEffect(() => {
+    const handler = (data: DrawioEditByViewerProps) => {
+      openDrawioModal(data.drawioMxFile, drawioMxFile => saveByDrawioModal(drawioMxFile, data.bol, data.eol));
+    };
+    window.globalEmitter.on('launchDrawioModal', handler);
+
+    return function cleanup() {
+      window.globalEmitter.removeListener('launchDrawioModal', handler);
+    };
+  }, [openDrawioModal, saveByDrawioModal]);
+
+  if (currentPage == null || isGuestUser == null || rendererOptions == null) {
     const entries = Object.entries({
     const entries = Object.entries({
-      currentPage, editorMode, isGuestUser, rendererOptions,
+      currentPage, isGuestUser, rendererOptions,
     })
     })
       .map(([key, value]) => [key, value == null ? 'null' : undefined])
       .map(([key, value]) => [key, value == null ? 'null' : undefined])
       .filter(([, value]) => value != null);
       .filter(([, value]) => value != null);
@@ -203,19 +125,22 @@ export const Page = (props) => {
     return null;
     return null;
   }
   }
 
 
+  const { _id: revisionId, body: markdown } = currentPage.revision;
+
   return (
   return (
-    <PageSubstance
-      {...props}
-      ref={pageRef}
-      rendererOptions={rendererOptions}
-      page={currentPage}
-      editorMode={editorMode}
-      isGuestUser={isGuestUser}
-      isMobile={isMobile}
-      isSlackEnabled={isSlackEnabled}
-      pageTags={pageTags}
-      slackChannels={slackChannelsData?.toString()}
-      mutateIsEnabledUnsavedWarning={mutateIsEnabledUnsavedWarning}
-    />
+    <div className={`mb-5 ${isMobile ? 'page-mobile' : ''}`}>
+
+      { revisionId != null && (
+        <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} />
+      )}
+
+      { !isGuestUser && (
+        <>
+          <GridEditModal />
+          <LinkEditModal />
+        </>
+      )}
+    </div>
   );
   );
+
 };
 };

+ 9 - 4
packages/app/src/components/ReactMarkdownComponents/DrawioViewerWithEditButton.tsx

@@ -1,7 +1,8 @@
 import React, { useCallback, useState } from 'react';
 import React, { useCallback, useState } from 'react';
 
 
 import {
 import {
-  DrawioViewer, DrawioViewerProps,
+  DrawioEditByViewerProps,
+  DrawioViewer, DrawioViewerProps, extractCodeFromMxfile,
 } from '@growi/remark-drawio-plugin';
 } from '@growi/remark-drawio-plugin';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
@@ -14,16 +15,20 @@ import styles from './DrawioViewerWithEditButton.module.scss';
 export const DrawioViewerWithEditButton = React.memo((props: DrawioViewerProps): JSX.Element => {
 export const DrawioViewerWithEditButton = React.memo((props: DrawioViewerProps): JSX.Element => {
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
+  const { bol, eol } = props;
+
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isSharedUser } = useIsSharedUser();
   const { data: isSharedUser } = useIsSharedUser();
-  const { open: openDrawioModal } = useDrawioModal();
 
 
   const [isRendered, setRendered] = useState(false);
   const [isRendered, setRendered] = useState(false);
   const [mxfile, setMxfile] = useState('');
   const [mxfile, setMxfile] = useState('');
 
 
   const editButtonClickHandler = useCallback(() => {
   const editButtonClickHandler = useCallback(() => {
-    openDrawioModal(mxfile);
-  }, [mxfile, openDrawioModal]);
+    const data: DrawioEditByViewerProps = {
+      bol, eol, drawioMxFile: extractCodeFromMxfile(mxfile),
+    };
+    window.globalEmitter.emit('launchDrawioModal', data);
+  }, [bol, eol, mxfile]);
 
 
   const renderingStartHandler = useCallback(() => {
   const renderingStartHandler = useCallback(() => {
     setRendered(false);
     setRendered(false);

+ 1 - 1
packages/app/src/stores/modal.tsx

@@ -472,7 +472,7 @@ export const useDrawioModal = (status?: DrawioModalStatus): SWRResponse<DrawioMo
   };
   };
 
 
   const close = (): void => {
   const close = (): void => {
-    swrResponse.mutate({ isOpened: false, drawioMxFile: '' });
+    swrResponse.mutate({ isOpened: false, drawioMxFile: '', onSave: undefined });
   };
   };
 
 
   return {
   return {

+ 8 - 2
packages/remark-drawio-plugin/src/components/DrawioViewer.tsx

@@ -20,13 +20,19 @@ declare global {
 
 
 export type DrawioViewerProps = {
 export type DrawioViewerProps = {
   diagramIndex: number,
   diagramIndex: number,
-  bol?: number,
-  eol?: number,
+  bol: number,
+  eol: number,
   children?: ReactNode,
   children?: ReactNode,
   onRenderingStart?: () => void,
   onRenderingStart?: () => void,
   onRenderingUpdated?: (mxfile: string | null) => void,
   onRenderingUpdated?: (mxfile: string | null) => void,
 }
 }
 
 
+export type DrawioEditByViewerProps = {
+  bol: number,
+  eol: number,
+  drawioMxFile: string,
+}
+
 export const DrawioViewer = React.memo((props: DrawioViewerProps): JSX.Element => {
 export const DrawioViewer = React.memo((props: DrawioViewerProps): JSX.Element => {
   const {
   const {
     diagramIndex, bol, eol, children,
     diagramIndex, bol, eol, children,