ConflictDiffModal.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import React, { useState, useRef, FC } from 'react';
  2. import PropTypes from 'prop-types';
  3. import {
  4. Modal, ModalHeader, ModalBody, ModalFooter,
  5. } from 'reactstrap';
  6. import { parseISO, format } from 'date-fns';
  7. import { useTranslation } from 'react-i18next';
  8. import { UnControlled as CodeMirror } from 'react-codemirror2';
  9. import PageContainer from '../../client/services/PageContainer';
  10. import EditorContainer from '../../client/services/EditorContainer';
  11. require('codemirror/mode/htmlmixed/htmlmixed');
  12. const DMP = require('diff_match_patch');
  13. Object.keys(DMP).forEach((key) => { window[key] = DMP[key] });
  14. type ConflictDiffModalProps = {
  15. isOpen: boolean | null;
  16. onCancel: (() => void) | null;
  17. pageContainer: PageContainer;
  18. editorContainer: EditorContainer;
  19. };
  20. export const ConflictDiffModal: FC<ConflictDiffModalProps> = (props) => {
  21. const { t } = useTranslation('');
  22. const resolvedRevision = useRef<string>(t('modal_resolve_conflict.resolve_conflict_message'));
  23. const [isRevisionselected, setIsRevisionSelected] = useState<boolean>(false);
  24. const { pageContainer, editorContainer } = props;
  25. const { request, origin, latest } = pageContainer.state.revisionsOnConflict || { request: {}, origin: {}, latest: {} };
  26. const codeMirrorRevisionOption = {
  27. mode: 'htmlmixed',
  28. lineNumbers: true,
  29. tabSize: 2,
  30. indentUnit: 2,
  31. readOnly: true,
  32. };
  33. const onCancel = () => {
  34. if (props.onCancel != null) {
  35. props.onCancel();
  36. }
  37. };
  38. const onResolveConflict = async() : Promise<void> => {
  39. // disable button after clicked
  40. setIsRevisionSelected(false);
  41. try {
  42. await pageContainer.resolveConflictAndReload(
  43. pageContainer.state.pageId,
  44. latest.revisionId,
  45. resolvedRevision.current, editorContainer.getCurrentOptionsToSave(),
  46. );
  47. }
  48. catch (error) {
  49. pageContainer.showErrorToastr(error);
  50. }
  51. };
  52. return (
  53. <Modal isOpen={props.isOpen || false} toggle={onCancel} className="modal-gfm-cheatsheet" size="xl">
  54. <ModalHeader tag="h4" toggle={onCancel} className="bg-primary text-light">
  55. <i className="icon-fw icon-exclamation" />{t('modal_resolve_conflict.resolve_conflict')}
  56. </ModalHeader>
  57. <ModalBody>
  58. {Object.keys(pageContainer.state.revisionsOnConflict || {}).length > 0
  59. && (
  60. <div className="row mx-2">
  61. <div className="col-12 text-center mt-2 mb-4">
  62. <h2 className="font-weight-bold">{t('modal_resolve_conflict.resolve_conflict_message')}</h2>
  63. </div>
  64. <div className="col-12 col-md-4 border border-dark">
  65. <h3 className="font-weight-bold my-2">{t('modal_resolve_conflict.requested_revision')}</h3>
  66. <div className="d-flex align-items-center my-3">
  67. <div>
  68. <img height="40px" className="rounded-circle" src={request.userImgPath} />
  69. </div>
  70. <div className="ml-3 text-muted">
  71. <p className="my-0">updated by {request.userName}</p>
  72. <p className="my-0">{format(parseISO(request.createdAt), 'yyyy/MM/dd HH:mm:ss')}</p>
  73. </div>
  74. </div>
  75. <CodeMirror
  76. value={request.revisionBody}
  77. options={codeMirrorRevisionOption}
  78. />
  79. <div className="text-center my-4">
  80. <button
  81. type="button"
  82. className="btn btn-primary"
  83. onClick={() => {
  84. setIsRevisionSelected(true);
  85. resolvedRevision.current = request.revisionBody;
  86. }}
  87. >
  88. <i className="icon-fw icon-arrow-down-circle"></i>
  89. {t('modal_resolve_conflict.select_revision', { revision: 'request' })}
  90. </button>
  91. </div>
  92. </div>
  93. <div className="col-12 col-md-4 border border-dark">
  94. <h3 className="font-weight-bold my-2">{t('origin_revision')}</h3>
  95. <div className="d-flex align-items-center my-3">
  96. <div>
  97. <img height="40px" className="rounded-circle" src={origin.userImgPath} />
  98. </div>
  99. <div className="ml-3 text-muted">
  100. <p className="my-0">updated by {origin.userName}</p>
  101. <p className="my-0">{format(parseISO(origin.createdAt), 'yyyy/MM/dd HH:mm:ss')}</p>
  102. </div>
  103. </div>
  104. <CodeMirror
  105. value={origin.revisionBody}
  106. options={codeMirrorRevisionOption}
  107. />
  108. <div className="text-center my-4">
  109. <button
  110. type="button"
  111. className="btn btn-primary"
  112. onClick={() => {
  113. setIsRevisionSelected(true);
  114. resolvedRevision.current = origin.revisionBody;
  115. }}
  116. >
  117. <i className="icon-fw icon-arrow-down-circle"></i>
  118. {t('modal_resolve_conflict.select_revision', { revision: 'origin' })}
  119. </button>
  120. </div>
  121. </div>
  122. <div className="col-12 col-md-4 border border-dark">
  123. <h3 className="font-weight-bold my-2">{t('modal_resolve_conflict.latest_revision')}</h3>
  124. <div className="d-flex align-items-center my-3">
  125. <div>
  126. <img height="40px" className="rounded-circle" src={latest.userImgPath} />
  127. </div>
  128. <div className="ml-3 text-muted">
  129. <p className="my-0">updated by {latest.userName}</p>
  130. <p className="my-0">{format(parseISO(latest.createdAt), 'yyyy/MM/dd HH:mm:ss')}</p>
  131. </div>
  132. </div>
  133. <CodeMirror
  134. value={latest.revisionBody}
  135. options={codeMirrorRevisionOption}
  136. />
  137. <div className="text-center my-4">
  138. <button
  139. type="button"
  140. className="btn btn-primary"
  141. onClick={() => {
  142. setIsRevisionSelected(true);
  143. resolvedRevision.current = latest.revisionBody;
  144. }}
  145. >
  146. <i className="icon-fw icon-arrow-down-circle"></i>
  147. {t('modal_resolve_conflict.select_revision', { revision: 'latest' })}
  148. </button>
  149. </div>
  150. </div>
  151. <div className="col-12 border border-dark">
  152. <h3 className="font-weight-bold my-2">{t('modal_resolve_conflict.selected_editable_revision')}</h3>
  153. <CodeMirror
  154. value={resolvedRevision.current}
  155. options={{
  156. mode: 'htmlmixed',
  157. lineNumbers: true,
  158. tabSize: 2,
  159. indentUnit: 2,
  160. }}
  161. onChange={(editor, data, pageBody) => {
  162. if (pageBody === '') setIsRevisionSelected(false);
  163. resolvedRevision.current = pageBody;
  164. }}
  165. />
  166. </div>
  167. </div>
  168. )
  169. }
  170. </ModalBody>
  171. <ModalFooter>
  172. <button
  173. type="button"
  174. className="btn btn-outline-secondary"
  175. onClick={onCancel}
  176. >
  177. {t('Cancel')}
  178. </button>
  179. <button
  180. type="button"
  181. className="btn btn-primary ml-3"
  182. onClick={onResolveConflict}
  183. disabled={!isRevisionselected}
  184. >
  185. {t('modal_resolve_conflict.resolve_and_save')}
  186. </button>
  187. </ModalFooter>
  188. </Modal>
  189. );
  190. };
  191. ConflictDiffModal.propTypes = {
  192. isOpen: PropTypes.bool,
  193. onCancel: PropTypes.func,
  194. pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
  195. editorContainer: PropTypes.instanceOf(EditorContainer).isRequired,
  196. };
  197. ConflictDiffModal.defaultProps = {
  198. isOpen: false,
  199. };