GrantSelector.jsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import { translate } from 'react-i18next';
  4. import FormGroup from 'react-bootstrap/es/FormGroup';
  5. import FormControl from 'react-bootstrap/es/FormControl';
  6. import ListGroup from 'react-bootstrap/es/ListGroup';
  7. import ListGroupItem from 'react-bootstrap/es/ListGroupItem';
  8. import Modal from 'react-bootstrap/es/Modal';
  9. const SPECIFIED_GROUP_VALUE = 'specifiedGroup';
  10. /**
  11. * Page grant select component
  12. *
  13. * @export
  14. * @class GrantSelector
  15. * @extends {React.Component}
  16. */
  17. class GrantSelector extends React.Component {
  18. constructor(props) {
  19. super(props);
  20. this.availableGrants = [
  21. {
  22. grant: 1, iconClass: 'icon-people', styleClass: '', label: 'Public',
  23. },
  24. {
  25. grant: 2, iconClass: 'icon-link', styleClass: 'text-info', label: 'Anyone with the link',
  26. },
  27. // { grant: 3, iconClass: '', label: 'Specified users only' },
  28. {
  29. grant: 4, iconClass: 'icon-lock', styleClass: 'text-danger', label: 'Just me',
  30. },
  31. {
  32. grant: 5, iconClass: 'icon-options', styleClass: '', label: 'Only inside the group',
  33. }, // appeared only one of these 'grant: 5'
  34. {
  35. grant: 5, iconClass: 'icon-options', styleClass: '', label: 'Reselect the group',
  36. }, // appeared only one of these 'grant: 5'
  37. ];
  38. this.state = {
  39. grant: this.props.grant || 1, // default: 1
  40. userRelatedGroups: [],
  41. isSelectGroupModalShown: false,
  42. };
  43. if (this.props.grantGroupId !== '') {
  44. this.state.grantGroup = {
  45. _id: this.props.grantGroupId,
  46. name: this.props.grantGroupName,
  47. };
  48. }
  49. // retrieve xss library from window
  50. this.xss = window.xss;
  51. this.getCurrentOptionsToSave = this.getCurrentOptionsToSave.bind(this);
  52. this.showSelectGroupModal = this.showSelectGroupModal.bind(this);
  53. this.hideSelectGroupModal = this.hideSelectGroupModal.bind(this);
  54. this.getGroupName = this.getGroupName.bind(this);
  55. this.changeGrantHandler = this.changeGrantHandler.bind(this);
  56. this.groupListItemClickHandler = this.groupListItemClickHandler.bind(this);
  57. }
  58. componentDidUpdate(prevProps, prevState) {
  59. /*
  60. * set SPECIFIED_GROUP_VALUE to grant selector
  61. * cz: bootstrap-select input element has the defferent state to React component
  62. */
  63. if (this.state.grantGroup != null) {
  64. this.grantSelectorInputEl.value = SPECIFIED_GROUP_VALUE;
  65. }
  66. // refresh bootstrap-select
  67. // see https://silviomoreto.github.io/bootstrap-select/methods/#selectpickerrefresh
  68. $('.grant-selector .selectpicker').selectpicker('refresh');
  69. // // DIRTY HACK -- 2018.05.25 Yuki Takei
  70. // set group name to the bootstrap-select options
  71. // cz: .selectpicker('refresh') doesn't replace data-content
  72. $('.grant-selector .group-name').text(this.getGroupName());
  73. }
  74. getCurrentOptionsToSave() {
  75. const options = {
  76. grant: this.state.grant,
  77. };
  78. if (this.state.grantGroup != null) {
  79. options.grantUserGroupId = this.state.grantGroup._id;
  80. }
  81. return options;
  82. }
  83. showSelectGroupModal() {
  84. this.retrieveUserGroupRelations();
  85. this.setState({ isSelectGroupModalShown: true });
  86. }
  87. hideSelectGroupModal() {
  88. this.setState({ isSelectGroupModalShown: false });
  89. }
  90. getGroupName() {
  91. const grantGroup = this.state.grantGroup;
  92. return grantGroup ? this.xss.process(grantGroup.name) : '';
  93. }
  94. /**
  95. * Retrieve user-group-relations data from backend
  96. */
  97. retrieveUserGroupRelations() {
  98. this.props.crowi.apiGet('/me/user-group-relations')
  99. .then((res) => {
  100. return res.userGroupRelations;
  101. })
  102. .then((userGroupRelations) => {
  103. const userRelatedGroups = userGroupRelations.map((relation) => {
  104. return relation.relatedGroup;
  105. });
  106. this.setState({ userRelatedGroups });
  107. });
  108. }
  109. /**
  110. * change event handler for grant selector
  111. */
  112. changeGrantHandler() {
  113. const grant = +this.grantSelectorInputEl.value;
  114. // select group
  115. if (grant === 5) {
  116. this.showSelectGroupModal();
  117. /*
  118. * reset grant selector to state
  119. */
  120. this.grantSelectorInputEl.value = this.state.grant;
  121. return;
  122. }
  123. this.setState({ grant, grantGroup: null });
  124. }
  125. groupListItemClickHandler(grantGroup) {
  126. this.setState({ grant: 5, grantGroup });
  127. // hide modal
  128. this.hideSelectGroupModal();
  129. }
  130. /**
  131. * Render grant selector DOM.
  132. * @returns
  133. * @memberof GrantSelector
  134. */
  135. renderGrantSelector() {
  136. const { t } = this.props;
  137. let index = 0;
  138. let selectedValue = this.state.grant;
  139. const grantElems = this.availableGrants.map((opt) => {
  140. const dataContent = `<i class="icon icon-fw ${opt.iconClass} ${opt.styleClass}"></i> <span class="${opt.styleClass}">${t(opt.label)}</span>`;
  141. return <option key={index++} value={opt.grant} data-content={dataContent}>{t(opt.label)}</option>;
  142. });
  143. const grantGroup = this.state.grantGroup;
  144. if (grantGroup != null) {
  145. selectedValue = SPECIFIED_GROUP_VALUE;
  146. // DIRTY HACK -- 2018.05.25 Yuki Takei
  147. // remove 'Only inside the group' item
  148. // cz: .selectpicker('refresh') doesn't replace data-content
  149. grantElems.splice(3, 1);
  150. }
  151. else {
  152. // DIRTY HACK -- 2018.05.25 Yuki Takei
  153. // remove 'Reselect the group' item
  154. // cz: .selectpicker('refresh') doesn't replace data-content
  155. grantElems.splice(4, 1);
  156. }
  157. /*
  158. * react-bootstrap couldn't be rendered only with React feature.
  159. * see also 'componentDidUpdate'
  160. */
  161. // add specified group option
  162. grantElems.push(
  163. <option
  164. ref="specifiedGroupOption"
  165. key="specifiedGroupKey"
  166. value={SPECIFIED_GROUP_VALUE}
  167. style={{ display: grantGroup ? 'inherit' : 'none' }}
  168. data-content={`<i class="icon icon-fw icon-organization text-success"></i> <span class="group-name text-success">${this.getGroupName()}</span>`}
  169. >
  170. {this.getGroupName()}
  171. </option>,
  172. );
  173. const bsClassName = 'form-control-dummy'; // set form-control* to shrink width
  174. return (
  175. <FormGroup className="grant-selector m-b-0">
  176. <FormControl
  177. componentClass="select"
  178. placeholder="select"
  179. defaultValue={selectedValue}
  180. bsClass={bsClassName}
  181. className="btn-group-sm selectpicker"
  182. onChange={this.changeGrantHandler}
  183. inputRef={(el) => { return this.grantSelectorInputEl = el }}
  184. >
  185. {grantElems}
  186. </FormControl>
  187. </FormGroup>
  188. );
  189. }
  190. /**
  191. * Render select grantgroup modal.
  192. *
  193. * @returns
  194. * @memberof GrantSelector
  195. */
  196. renderSelectGroupModal() {
  197. const generateGroupListItems = () => {
  198. return this.state.userRelatedGroups.map((group) => {
  199. return (
  200. <ListGroupItem key={group._id} header={group.name} onClick={() => { this.groupListItemClickHandler(group) }}>
  201. (TBD) List group members
  202. </ListGroupItem>
  203. );
  204. });
  205. };
  206. const content = this.state.userRelatedGroups.length === 0
  207. ? (
  208. <div>
  209. <h4>There is no group to which you belong.</h4>
  210. { this.props.crowi.isAdmin
  211. && <p><a href="/admin/user-groups"><i className="icon icon-fw icon-login"></i> Manage Groups</a></p>
  212. }
  213. </div>
  214. )
  215. : (
  216. <ListGroup>
  217. {generateGroupListItems()}
  218. </ListGroup>
  219. );
  220. return (
  221. <Modal
  222. className="select-grant-group"
  223. container={this}
  224. show={this.state.isSelectGroupModalShown}
  225. onHide={this.hideSelectGroupModal}
  226. >
  227. <Modal.Header closeButton>
  228. <Modal.Title>
  229. Select a Group
  230. </Modal.Title>
  231. </Modal.Header>
  232. <Modal.Body>
  233. {content}
  234. </Modal.Body>
  235. </Modal>
  236. );
  237. }
  238. render() {
  239. return (
  240. <React.Fragment>
  241. {this.renderGrantSelector()}
  242. {this.renderSelectGroupModal()}
  243. </React.Fragment>
  244. );
  245. }
  246. }
  247. GrantSelector.propTypes = {
  248. t: PropTypes.func.isRequired, // i18next
  249. crowi: PropTypes.object.isRequired,
  250. grant: PropTypes.number,
  251. grantGroupId: PropTypes.string,
  252. grantGroupName: PropTypes.string,
  253. };
  254. export default translate()(GrantSelector);