GrowiZipImportItem.jsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. // eslint-disable-next-line no-unused-vars
  4. import { withTranslation } from 'react-i18next';
  5. import ProgressBar from 'react-bootstrap/es/ProgressBar';
  6. import GrowiArchiveImportOption from '@commons/models/admin/growi-archive-import-option';
  7. const MODE_ATTR_MAP = {
  8. insert: { color: 'info', icon: 'icon-plus', label: 'Insert' },
  9. upsert: { color: 'success', icon: 'icon-plus', label: 'Upsert' },
  10. flushAndInsert: { color: 'danger', icon: 'icon-refresh', label: 'Flush and Insert' },
  11. };
  12. export const DEFAULT_MODE = 'insert';
  13. export const MODE_RESTRICTED_COLLECTION = {
  14. configs: ['flushAndInsert'],
  15. users: ['insert', 'upsert'],
  16. };
  17. export default class GrowiZipImportItem extends React.Component {
  18. constructor(props) {
  19. super(props);
  20. this.changeHandler = this.changeHandler.bind(this);
  21. this.modeSelectedHandler = this.modeSelectedHandler.bind(this);
  22. this.configButtonClickedHandler = this.configButtonClickedHandler.bind(this);
  23. this.errorLinkClickedHandler = this.errorLinkClickedHandler.bind(this);
  24. }
  25. changeHandler(e) {
  26. const { collectionName, onChange } = this.props;
  27. if (onChange != null) {
  28. onChange(collectionName, e.target.checked);
  29. }
  30. }
  31. modeSelectedHandler(mode) {
  32. const { collectionName, onOptionChange } = this.props;
  33. if (onOptionChange == null) {
  34. return;
  35. }
  36. onOptionChange(collectionName, { mode });
  37. }
  38. configButtonClickedHandler() {
  39. const { collectionName, onConfigButtonClicked } = this.props;
  40. if (onConfigButtonClicked == null) {
  41. return;
  42. }
  43. onConfigButtonClicked(collectionName);
  44. }
  45. errorLinkClickedHandler() {
  46. const { collectionName, onErrorLinkClicked } = this.props;
  47. if (onErrorLinkClicked == null) {
  48. return;
  49. }
  50. onErrorLinkClicked(collectionName);
  51. }
  52. renderModeLabel(mode, isColorized = false) {
  53. const attrMap = MODE_ATTR_MAP[mode];
  54. const className = isColorized ? `text-${attrMap.color}` : '';
  55. return <span className={className}><i className={attrMap.icon}></i> {attrMap.label}</span>;
  56. }
  57. renderCheckbox() {
  58. const {
  59. collectionName, isSelected, isImporting,
  60. } = this.props;
  61. return (
  62. <div className="checkbox checkbox-info my-0">
  63. <input
  64. type="checkbox"
  65. id={collectionName}
  66. name={collectionName}
  67. className="form-check-input"
  68. value={collectionName}
  69. checked={isSelected}
  70. disabled={isImporting}
  71. onChange={this.changeHandler}
  72. />
  73. <label className="text-capitalize form-check-label" htmlFor={collectionName}>
  74. {collectionName}
  75. </label>
  76. </div>
  77. );
  78. }
  79. renderModeSelector() {
  80. const {
  81. collectionName, option, isImporting,
  82. } = this.props;
  83. const attrMap = MODE_ATTR_MAP[option.mode];
  84. const btnColor = `btn-${attrMap.color}`;
  85. const modes = MODE_RESTRICTED_COLLECTION[collectionName] || Object.keys(MODE_ATTR_MAP);
  86. return (
  87. <span className="d-inline-flex align-items-center">
  88. Mode:&nbsp;
  89. <div className="dropdown d-inline-block">
  90. <button
  91. className={`btn ${btnColor} btn-xs dropdown-toggle`}
  92. type="button"
  93. id="ddmMode"
  94. disabled={isImporting}
  95. data-toggle="dropdown"
  96. aria-haspopup="true"
  97. aria-expanded="true"
  98. >
  99. {this.renderModeLabel(option.mode)}
  100. <span className="caret ml-2"></span>
  101. </button>
  102. <ul className="dropdown-menu" aria-labelledby="ddmMode">
  103. { modes.map((mode) => {
  104. return (
  105. <li key={`buttonMode_${mode}`}>
  106. <a type="button" role="button" onClick={() => this.modeSelectedHandler(mode)}>
  107. {this.renderModeLabel(mode, true)}
  108. </a>
  109. </li>
  110. );
  111. }) }
  112. </ul>
  113. </div>
  114. </span>
  115. );
  116. }
  117. renderConfigButton() {
  118. const { isImporting, isConfigButtonAvailable } = this.props;
  119. return (
  120. <button
  121. type="button"
  122. className="btn btn-default btn-xs ml-2"
  123. disabled={isImporting || !isConfigButtonAvailable}
  124. onClick={isConfigButtonAvailable ? this.configButtonClickedHandler : null}
  125. >
  126. <i className="icon-settings"></i>
  127. </button>
  128. );
  129. }
  130. renderProgressBar() {
  131. const {
  132. isImporting, insertedCount, modifiedCount, errorsCount,
  133. } = this.props;
  134. const total = insertedCount + modifiedCount + errorsCount;
  135. return (
  136. <ProgressBar className="mb-0">
  137. <ProgressBar max={total} striped={isImporting} active={isImporting} now={insertedCount} bsStyle="info" />
  138. <ProgressBar max={total} striped={isImporting} active={isImporting} now={modifiedCount} bsStyle="success" />
  139. <ProgressBar max={total} striped={isImporting} active={isImporting} now={errorsCount} bsStyle="danger" />
  140. </ProgressBar>
  141. );
  142. }
  143. renderBody() {
  144. const { isImporting, isImported } = this.props;
  145. if (!isImporting && !isImported) {
  146. return 'Ready';
  147. }
  148. const { insertedCount, modifiedCount, errorsCount } = this.props;
  149. return (
  150. <div className="w-100 text-center">
  151. <span className="text-info"><strong>{insertedCount}</strong> Inserted</span>,&nbsp;
  152. <span className="text-success"><strong>{modifiedCount}</strong> Modified</span>,&nbsp;
  153. { errorsCount > 0
  154. ? <a className="text-danger" role="button" onClick={this.errorLinkClickedHandler}><u><strong>{errorsCount}</strong> Failed</u></a>
  155. : <span className="text-muted"><strong>0</strong> Failed</span>
  156. }
  157. </div>
  158. );
  159. }
  160. render() {
  161. const {
  162. isSelected,
  163. } = this.props;
  164. return (
  165. <div className="panel panel-default">
  166. <div className="panel-heading">
  167. <div className="d-flex justify-content-between align-items-center">
  168. {/* left */}
  169. {this.renderCheckbox()}
  170. {/* right */}
  171. <span className="d-flex align-items-center">
  172. {this.renderModeSelector()}
  173. {this.renderConfigButton()}
  174. </span>
  175. </div>
  176. </div>
  177. { isSelected && (
  178. <>
  179. {this.renderProgressBar()}
  180. <div className="panel-body">
  181. {this.renderBody()}
  182. </div>
  183. </>
  184. ) }
  185. </div>
  186. );
  187. }
  188. }
  189. GrowiZipImportItem.propTypes = {
  190. collectionName: PropTypes.string.isRequired,
  191. isSelected: PropTypes.bool.isRequired,
  192. option: PropTypes.instanceOf(GrowiArchiveImportOption).isRequired,
  193. isImporting: PropTypes.bool.isRequired,
  194. isImported: PropTypes.bool.isRequired,
  195. insertedCount: PropTypes.number,
  196. modifiedCount: PropTypes.number,
  197. errorsCount: PropTypes.number,
  198. isConfigButtonAvailable: PropTypes.bool,
  199. onChange: PropTypes.func,
  200. onOptionChange: PropTypes.func,
  201. onConfigButtonClicked: PropTypes.func,
  202. onErrorLinkClicked: PropTypes.func,
  203. };
  204. GrowiZipImportItem.defaultProps = {
  205. insertedCount: 0,
  206. modifiedCount: 0,
  207. errorsCount: 0,
  208. };