PageStatusAlert.jsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import { useTranslation } from 'react-i18next';
  4. import AppContainer from '~/client/services/AppContainer';
  5. import PageContainer from '~/client/services/PageContainer';
  6. import { withUnstatedContainers } from './UnstatedUtils';
  7. /**
  8. *
  9. * @author Yuki Takei <yuki@weseek.co.jp>
  10. *
  11. * @export
  12. * @class PageStatusAlert
  13. * @extends {React.Component}
  14. */
  15. class PageStatusAlert extends React.Component {
  16. constructor(props) {
  17. super(props);
  18. this.state = {
  19. };
  20. this.getContentsForSomeoneEditingAlert = this.getContentsForSomeoneEditingAlert.bind(this);
  21. this.getContentsForDraftExistsAlert = this.getContentsForDraftExistsAlert.bind(this);
  22. this.getContentsForUpdatedAlert = this.getContentsForUpdatedAlert.bind(this);
  23. this.onClickResolveConflict = this.onClickResolveConflict.bind(this);
  24. }
  25. refreshPage() {
  26. window.location.reload();
  27. }
  28. onClickResolveConflict() {
  29. this.props.pageContainer.setState({
  30. isConflictDiffModalOpen: true,
  31. });
  32. }
  33. getContentsForSomeoneEditingAlert() {
  34. const { t } = this.props;
  35. return [
  36. ['bg-success', 'd-hackmd-none'],
  37. <>
  38. <i className="icon-fw icon-people"></i>
  39. {t('hackmd.someone_editing')}
  40. </>,
  41. <a href="#hackmd" className="btn btn-outline-white">
  42. <i className="fa fa-fw fa-file-text-o mr-1"></i>
  43. Open HackMD Editor
  44. </a>,
  45. ];
  46. }
  47. getContentsForDraftExistsAlert(isRealtime) {
  48. const { t } = this.props;
  49. return [
  50. ['bg-success', 'd-hackmd-none'],
  51. <>
  52. <i className="icon-fw icon-pencil"></i>
  53. {t('hackmd.this_page_has_draft')}
  54. </>,
  55. <a href="#hackmd" className="btn btn-outline-white">
  56. <i className="fa fa-fw fa-file-text-o mr-1"></i>
  57. Open HackMD Editor
  58. </a>,
  59. ];
  60. }
  61. getContentsForUpdatedAlert() {
  62. const { t, appContainer, pageContainer } = this.props;
  63. const pageEditor = appContainer.getComponentInstance('PageEditor');
  64. let isConflictOnEdit = false;
  65. if (pageEditor != null) {
  66. const markdownOnEdit = pageEditor.getMarkdown();
  67. isConflictOnEdit = markdownOnEdit !== pageContainer.state.markdown;
  68. }
  69. const label1 = isConflictOnEdit
  70. ? t('modal_resolve_conflict.file_conflicting_with_newer_remote')
  71. : `${pageContainer.state.lastUpdateUsername} ${t('edited this page')}`;
  72. return [
  73. ['bg-warning'],
  74. <>
  75. <i className="icon-fw icon-bulb"></i>
  76. {label1}
  77. </>,
  78. <>
  79. <button type="button" onClick={() => this.refreshPage()} className="btn btn-outline-white mr-4">
  80. <i className="icon-fw icon-reload mr-1"></i>
  81. {t('Load latest')}
  82. </button>
  83. { isConflictOnEdit && (
  84. <button
  85. type="button"
  86. onClick={this.onClickResolveConflict}
  87. className="btn btn-outline-white"
  88. >
  89. <i className="fa fa-fw fa-file-text-o mr-1"></i>
  90. {t('modal_resolve_conflict.resolve_conflict')}
  91. </button>
  92. )}
  93. </>,
  94. ];
  95. }
  96. render() {
  97. const {
  98. revisionId, revisionIdHackmdSynced, remoteRevisionId, hasDraftOnHackmd, isHackmdDraftUpdatingInRealtime,
  99. } = this.props.pageContainer.state;
  100. const isRevisionOutdated = revisionId !== remoteRevisionId;
  101. const isHackmdDocumentOutdated = revisionIdHackmdSynced !== remoteRevisionId;
  102. let getContentsFunc = null;
  103. // when remote revision is newer than both
  104. if (isHackmdDocumentOutdated && isRevisionOutdated) {
  105. getContentsFunc = this.getContentsForUpdatedAlert;
  106. }
  107. // when someone editing with HackMD
  108. else if (isHackmdDraftUpdatingInRealtime) {
  109. getContentsFunc = this.getContentsForSomeoneEditingAlert;
  110. }
  111. // when the draft of HackMD is newest
  112. else if (hasDraftOnHackmd) {
  113. getContentsFunc = this.getContentsForDraftExistsAlert;
  114. }
  115. // do not render anything
  116. else {
  117. return null;
  118. }
  119. const [additionalClasses, label, btn] = getContentsFunc();
  120. return (
  121. <div className={`card grw-page-status-alert text-white fixed-bottom animated fadeInUp faster ${additionalClasses.join(' ')}`}>
  122. <div className="card-body">
  123. <p className="card-text grw-card-label-container">
  124. {label}
  125. </p>
  126. <p className="card-text grw-card-btn-container">
  127. {btn}
  128. </p>
  129. </div>
  130. </div>
  131. );
  132. }
  133. }
  134. PageStatusAlert.propTypes = {
  135. t: PropTypes.func.isRequired, // i18next
  136. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  137. pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
  138. };
  139. const PageStatusAlertWrapperFC = (props) => {
  140. const { t } = useTranslation();
  141. return <PageStatusAlert t={t} {...props} />;
  142. };
  143. /**
  144. * Wrapper component for using unstated
  145. */
  146. const PageStatusAlertWrapper = withUnstatedContainers(PageStatusAlertWrapperFC, [AppContainer, PageContainer]);
  147. export default PageStatusAlertWrapper;