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

Merge pull request #2645 from weseek/imprv/suspence-retrieve-history

Imprv/suspence retrieve history
Yuki Takei 5 лет назад
Родитель
Сommit
63b2147146

+ 3 - 1
src/client/js/app.jsx

@@ -32,6 +32,7 @@ import TableOfContents from './components/TableOfContents';
 import PersonalSettings from './components/Me/PersonalSettings';
 import NavigationContainer from './services/NavigationContainer';
 import PageContainer from './services/PageContainer';
+import PageHistoryContainer from './services/PageHistoryContainer';
 import CommentContainer from './services/CommentContainer';
 import EditorContainer from './services/EditorContainer';
 import TagContainer from './services/TagContainer';
@@ -51,12 +52,13 @@ const socketIoContainer = appContainer.getContainer('SocketIoContainer');
 // create unstated container instance
 const navigationContainer = new NavigationContainer(appContainer);
 const pageContainer = new PageContainer(appContainer);
+const pageHistoryContainer = new PageHistoryContainer(appContainer, pageContainer);
 const commentContainer = new CommentContainer(appContainer);
 const editorContainer = new EditorContainer(appContainer, defaultEditorOptions, defaultPreviewOptions);
 const tagContainer = new TagContainer(appContainer);
 const personalContainer = new PersonalContainer(appContainer);
 const injectableContainers = [
-  appContainer, socketIoContainer, navigationContainer, pageContainer, commentContainer, editorContainer, tagContainer, personalContainer,
+  appContainer, socketIoContainer, navigationContainer, pageContainer, pageHistoryContainer, commentContainer, editorContainer, tagContainer, personalContainer,
 ];
 
 logger.info('unstated containers have been initialized');

+ 31 - 161
src/client/js/components/PageHistory.jsx

@@ -2,185 +2,55 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import loggerFactory from '@alias/logger';
 
-import { withTranslation } from 'react-i18next';
 import { withUnstatedContainers } from './UnstatedUtils';
+import { toastError } from '../util/apiNotification';
 
+import { withLoadingSppiner } from './SuspenseUtils';
 import PageRevisionList from './PageHistory/PageRevisionList';
-import AppContainer from '../services/AppContainer';
-import PageContainer from '../services/PageContainer';
 
-const logger = loggerFactory('growi:PageHistory');
-class PageHistory extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      // TODO use suspense
-      isLoaded: false,
-      isLoading: false,
-      errorMessage: null,
-      revisions: [],
-      diffOpened: {},
-    };
-
-    this.getPreviousRevision = this.getPreviousRevision.bind(this);
-    this.onDiffOpenClicked = this.onDiffOpenClicked.bind(this);
-  }
+import PageHistroyContainer from '../services/PageHistoryContainer';
 
-  async componentWillMount() {
-    const { appContainer, pageContainer } = this.props;
-    const { shareLinkId, pageId } = pageContainer.state;
-
-    if (!pageId) {
-      return;
-    }
+const logger = loggerFactory('growi:PageHistory');
 
-    let res;
-    try {
-      this.setState({ isLoading: true });
-      res = await appContainer.apiv3Get('/revisions/list', { pageId, share_link_id: shareLinkId });
-    }
-    catch (err) {
-      logger.error(err);
-      this.setState({ errorMessage: err });
-      return;
-    }
-    finally {
-      this.setState({ isLoading: false });
-    }
-    const rev = res.data.revisions;
-    const diffOpened = {};
-    const lastId = rev.length - 1;
 
-    res.data.revisions.forEach((revision, i) => {
-      const user = revision.author;
-      if (user) {
-        rev[i].author = user;
-      }
+function PageHistory(props) {
+  const { pageHistoryContainer } = props;
 
-      if (i === 0 || i === lastId) {
-        diffOpened[revision._id] = true;
+  if (pageHistoryContainer.state.revisions === pageHistoryContainer.dummyRevisions) {
+    throw new Promise(async() => {
+      try {
+        await props.pageHistoryContainer.retrieveRevisions();
       }
-      else {
-        diffOpened[revision._id] = false;
+      catch (err) {
+        toastError(err);
+        pageHistoryContainer.setState({ retrieveError: err.message });
+        logger.error(err);
       }
     });
-
-    this.setState({
-      isLoaded: true,
-      revisions: rev,
-      diffOpened,
-    });
-
-    // load 0, and last default
-    if (rev[0]) {
-      this.fetchPageRevisionBody(rev[0]);
-    }
-    if (rev[1]) {
-      this.fetchPageRevisionBody(rev[1]);
-    }
-    if (lastId !== 0 && lastId !== 1 && rev[lastId]) {
-      this.fetchPageRevisionBody(rev[lastId]);
-    }
   }
 
-  getPreviousRevision(currentRevision) {
-    let cursor = null;
-    for (const revision of this.state.revisions) {
-      // comparing ObjectId
-      // eslint-disable-next-line eqeqeq
-      if (cursor && cursor._id == currentRevision._id) {
-        cursor = revision;
-        break;
-      }
-
-      cursor = revision;
-    }
-
-    return cursor;
-  }
-
-  onDiffOpenClicked(revision) {
-    const diffOpened = this.state.diffOpened;
-    const revisionId = revision._id;
-
-    diffOpened[revisionId] = !(diffOpened[revisionId]);
-    this.setState({
-      diffOpened,
-    });
-
-    this.fetchPageRevisionBody(revision);
-    this.fetchPageRevisionBody(this.getPreviousRevision(revision));
-  }
-
-  fetchPageRevisionBody(revision) {
-    const { appContainer, pageContainer } = this.props;
-    const { shareLinkId, pageId } = pageContainer.state;
-
-    if (revision.body) {
-      return;
-    }
-
-    appContainer.apiGet('/revisions.get',
-      { page_id: pageId, revision_id: revision._id, share_link_id: shareLinkId })
-      .then((res) => {
-        if (res.ok) {
-          this.setState({
-            revisions: this.state.revisions.map((rev) => {
-              // comparing ObjectId
-              // eslint-disable-next-line eqeqeq
-              if (rev._id == res.revision._id) {
-                return res.revision;
-              }
-
-              return rev;
-            }),
-          });
-        }
-      })
-      .catch((err) => {
-
-      });
-  }
-
-  render() {
-    return (
-      <div className="mt-4">
-        { this.state.isLoading && (
-          <div className="my-5 text-center">
-            <i className="fa fa-lg fa-spinner fa-pulse mx-auto text-muted"></i>
-          </div>
-        ) }
-        { this.state.errorMessage && (
-          <div className="my-5">
-            <div className="text-danger">{this.state.errorMessage}</div>
-          </div>
-        ) }
-        { this.state.isLoaded && (
-          <PageRevisionList
-            t={this.props.t}
-            revisions={this.state.revisions}
-            diffOpened={this.state.diffOpened}
-            getPreviousRevision={this.getPreviousRevision}
-            onDiffOpenClicked={this.onDiffOpenClicked}
-          />
-        ) }
+  return (
+    <div className="mt-4">
+      {pageHistoryContainer.state.errorMessage && (
+      <div className="my-5">
+        <div className="text-danger">{pageHistoryContainer.state.errorMessage}</div>
       </div>
-    );
-  }
+        ) }
+      <PageRevisionList
+        revisions={pageHistoryContainer.state.revisions}
+        diffOpened={pageHistoryContainer.state.diffOpened}
+        getPreviousRevision={pageHistoryContainer.getPreviousRevision}
+        onDiffOpenClicked={pageHistoryContainer.onDiffOpenClicked}
+      />
+    </div>
+  );
 
 }
 
-const PageHistoryWrapper = withUnstatedContainers(PageHistory, [AppContainer, PageContainer]);
-
+const RenderPageHistoryWrapper = withUnstatedContainers(withLoadingSppiner(PageHistory), [PageHistroyContainer]);
 
 PageHistory.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
-
-  t: PropTypes.func.isRequired, // i18next
-
+  pageHistoryContainer: PropTypes.instanceOf(PageHistroyContainer).isRequired,
 };
 
-export default withTranslation()(PageHistoryWrapper);
+export default RenderPageHistoryWrapper;

+ 6 - 3
src/client/js/components/PageHistory/PageRevisionList.jsx

@@ -1,10 +1,12 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 
+import { withTranslation } from 'react-i18next';
+
 import Revision from './Revision';
 import RevisionDiff from './RevisionDiff';
 
-export default class PageRevisionList extends React.Component {
+class PageRevisionList extends React.Component {
 
   constructor(props) {
     super(props);
@@ -65,8 +67,6 @@ export default class PageRevisionList extends React.Component {
     const { t } = this.props;
 
     const revisions = this.props.revisions;
-
-
     const revisionCount = this.props.revisions.length;
 
     let hasDiffPrev;
@@ -117,7 +117,10 @@ export default class PageRevisionList extends React.Component {
 
 PageRevisionList.propTypes = {
   t: PropTypes.func.isRequired, // i18next
+
   revisions: PropTypes.array,
   diffOpened: PropTypes.object,
   onDiffOpenClicked: PropTypes.func.isRequired,
 };
+
+export default withTranslation()(PageRevisionList);

+ 21 - 0
src/client/js/components/SuspenseUtils.jsx

@@ -0,0 +1,21 @@
+/* eslint-disable import/prefer-default-export */
+import React, { Suspense } from 'react';
+
+/**
+ * If you throw a Promise in the component, it will display a sppiner
+ * @param {object} Component A React.Component or functional component
+ */
+export function withLoadingSppiner(Component) {
+  return (props => (
+    // wrap with <Suspense></Suspense>
+    <Suspense
+      fallback={(
+        <div className="my-5 text-center">
+          <i className="fa fa-lg fa-spinner fa-pulse mx-auto text-muted"></i>
+        </div>
+      )}
+    >
+      <Component {...props} />;
+    </Suspense>
+  ));
+}

+ 138 - 0
src/client/js/services/PageHistoryContainer.js

@@ -0,0 +1,138 @@
+import { Container } from 'unstated';
+
+/**
+ * Service container for personal settings page (PageHistory.jsx)
+ * @extends {Container} unstated Container
+ */
+export default class PageHistoryContainer extends Container {
+
+  constructor(appContainer, pageContainer) {
+    super();
+
+    this.appContainer = appContainer;
+    this.pageContainer = pageContainer;
+
+    this.dummyRevisions = 0;
+
+    this.state = {
+      retrieveError: null,
+
+      // set dummy rivisions for using suspense
+      revisions: this.dummyRevisions,
+      diffOpened: {},
+    };
+
+    this.retrieveRevisions = this.retrieveRevisions.bind(this);
+    this.onDiffOpenClicked = this.onDiffOpenClicked.bind(this);
+    this.getPreviousRevision = this.getPreviousRevision.bind(this);
+    this.fetchPageRevisionBody = this.fetchPageRevisionBody.bind(this);
+  }
+
+  /**
+   * Workaround for the mangling in production build to break constructor.name
+   */
+  static getClassName() {
+    return 'PageHistoryContainer';
+  }
+
+  async retrieveRevisions() {
+    const { pageId, shareLinkId } = this.pageContainer.state;
+
+    if (!pageId) {
+      return;
+    }
+
+    const res = await this.appContainer.apiv3Get('/revisions/list', { pageId, share_link_id: shareLinkId });
+    const rev = res.data.revisions;
+    const diffOpened = {};
+    const lastId = rev.length - 1;
+
+    res.data.revisions.forEach((revision, i) => {
+      const user = revision.author;
+      if (user) {
+        rev[i].author = user;
+      }
+
+      if (i === 0 || i === lastId) {
+        diffOpened[revision._id] = true;
+      }
+      else {
+        diffOpened[revision._id] = false;
+      }
+    });
+
+    this.setState({ revisions: rev });
+    this.setState({ diffOpened });
+
+    // load 0, and last default
+    if (rev[0]) {
+      this.fetchPageRevisionBody(rev[0]);
+    }
+    if (rev[1]) {
+      this.fetchPageRevisionBody(rev[1]);
+    }
+    if (lastId !== 0 && lastId !== 1 && rev[lastId]) {
+      this.fetchPageRevisionBody(rev[lastId]);
+    }
+
+    return;
+  }
+
+  onDiffOpenClicked(revision) {
+    const { diffOpened } = this.state;
+    const revisionId = revision._id;
+
+    diffOpened[revisionId] = !(diffOpened[revisionId]);
+    this.setState(diffOpened);
+
+    this.fetchPageRevisionBody(revision);
+    this.fetchPageRevisionBody(this.getPreviousRevision(revision));
+  }
+
+  getPreviousRevision(currentRevision) {
+    let cursor = null;
+    for (const revision of this.state.revisions) {
+      // comparing ObjectId
+      // eslint-disable-next-line eqeqeq
+      if (cursor && cursor._id == currentRevision._id) {
+        cursor = revision;
+        break;
+      }
+
+      cursor = revision;
+    }
+
+    return cursor;
+  }
+
+  fetchPageRevisionBody(revision) {
+    const { pageId, shareLinkId } = this.pageContainer.state;
+
+    if (revision.body) {
+      return;
+    }
+
+    // TODO GW-3487 apiV3
+    this.appContainer.apiGet('/revisions.get', { page_id: pageId, revision_id: revision._id, share_link_id: shareLinkId })
+      .then((res) => {
+        if (res.ok) {
+          this.setState({
+            revisions: this.state.revisions.map((rev) => {
+              // comparing ObjectId
+              // eslint-disable-next-line eqeqeq
+              if (rev._id == res.revision._id) {
+                return res.revision;
+              }
+
+              return rev;
+            }),
+          });
+        }
+      })
+      .catch((err) => {
+
+      });
+  }
+
+
+}