OptionsSelector.jsx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import { withTranslation } from 'react-i18next';
  4. import {
  5. FormGroup, Label, Input,
  6. Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
  7. } from 'reactstrap';
  8. import { createSubscribedElement } from '../UnstatedUtils';
  9. import EditorContainer from '../../services/EditorContainer';
  10. export const defaultEditorOptions = {
  11. theme: 'elegant',
  12. keymapMode: 'default',
  13. styleActiveLine: false,
  14. };
  15. export const defaultPreviewOptions = {
  16. renderMathJaxInRealtime: false,
  17. };
  18. class OptionsSelector extends React.Component {
  19. constructor(props) {
  20. super(props);
  21. const config = this.props.crowi.getConfig();
  22. const isMathJaxEnabled = !!config.env.MATHJAX;
  23. this.state = {
  24. isCddMenuOpened: false,
  25. isMathJaxEnabled,
  26. };
  27. this.availableThemes = [
  28. 'eclipse', 'elegant', 'neo', 'mdn-like', 'material', 'dracula', 'monokai', 'twilight',
  29. ];
  30. this.keymapModes = {
  31. default: 'Default',
  32. vim: 'Vim',
  33. emacs: 'Emacs',
  34. sublime: 'Sublime Text',
  35. };
  36. this.onChangeTheme = this.onChangeTheme.bind(this);
  37. this.onChangeKeymapMode = this.onChangeKeymapMode.bind(this);
  38. this.onClickStyleActiveLine = this.onClickStyleActiveLine.bind(this);
  39. this.onClickRenderMathJaxInRealtime = this.onClickRenderMathJaxInRealtime.bind(this);
  40. this.onToggleConfigurationDropdown = this.onToggleConfigurationDropdown.bind(this);
  41. }
  42. componentDidMount() {
  43. this.init();
  44. }
  45. init() {
  46. const { editorContainer } = this.props;
  47. this.themeSelectorInputEl.value = editorContainer.state.editorOptions.theme;
  48. this.keymapModeSelectorInputEl.value = editorContainer.state.editorOptions.keymapMode;
  49. }
  50. onChangeTheme() {
  51. const { editorContainer } = this.props;
  52. const newValue = this.themeSelectorInputEl.value;
  53. const newOpts = Object.assign(editorContainer.state.editorOptions, { theme: newValue });
  54. editorContainer.setState({ editorOptions: newOpts });
  55. // save to localStorage
  56. editorContainer.saveOptsToLocalStorage();
  57. }
  58. onChangeKeymapMode() {
  59. const { editorContainer } = this.props;
  60. const newValue = this.keymapModeSelectorInputEl.value;
  61. const newOpts = Object.assign(editorContainer.state.editorOptions, { keymapMode: newValue });
  62. editorContainer.setState({ editorOptions: newOpts });
  63. // save to localStorage
  64. editorContainer.saveOptsToLocalStorage();
  65. }
  66. onClickStyleActiveLine(event) {
  67. const { editorContainer } = this.props;
  68. // keep dropdown opened
  69. this._cddForceOpen = true;
  70. const newValue = !editorContainer.state.editorOptions.styleActiveLine;
  71. const newOpts = Object.assign(editorContainer.state.editorOptions, { styleActiveLine: newValue });
  72. editorContainer.setState({ editorOptions: newOpts });
  73. // save to localStorage
  74. editorContainer.saveOptsToLocalStorage();
  75. }
  76. onClickRenderMathJaxInRealtime(event) {
  77. const { editorContainer } = this.props;
  78. const newValue = !editorContainer.state.previewOptions.renderMathJaxInRealtime;
  79. const newOpts = Object.assign(editorContainer.state.previewOptions, { renderMathJaxInRealtime: newValue });
  80. editorContainer.setState({ previewOptions: newOpts });
  81. // save to localStorage
  82. editorContainer.saveOptsToLocalStorage();
  83. }
  84. onToggleConfigurationDropdown(newValue) {
  85. this.setState({ isCddMenuOpened: !this.state.isCddMenuOpened });
  86. }
  87. renderThemeSelector() {
  88. const optionElems = this.availableThemes.map((theme) => {
  89. return <option key={theme} value={theme}>{theme}</option>;
  90. });
  91. return (
  92. <FormGroup className="my-0">
  93. <Label>Theme:</Label>
  94. <Input
  95. type="select"
  96. bsSize="sm"
  97. placeholder="select"
  98. className="selectpicker"
  99. cssModule={{ width: 'unset' }}
  100. onChange={this.onChangeTheme}
  101. // eslint-disable-next-line no-return-assign
  102. innerRef={(el) => { return this.themeSelectorInputEl = el }}
  103. >
  104. {optionElems}
  105. </Input>
  106. </FormGroup>
  107. );
  108. }
  109. renderKeymapModeSelector() {
  110. const optionElems = [];
  111. // eslint-disable-next-line guard-for-in, no-restricted-syntax
  112. for (const mode in this.keymapModes) {
  113. const label = this.keymapModes[mode];
  114. const dataContent = (mode === 'default')
  115. ? label
  116. : `<img src='/images/icons/${mode}.png' width='16px' class='m-r-5'></img> ${label}`;
  117. optionElems.push(
  118. <option key={mode} value={mode} data-content={dataContent}>{label}</option>,
  119. );
  120. }
  121. return (
  122. <FormGroup className="my-0">
  123. <Label>Keymap:</Label>
  124. <Input
  125. type="select"
  126. bsSize="sm"
  127. placeholder="select"
  128. className="selectpicker"
  129. onChange={this.onChangeKeymapMode}
  130. // eslint-disable-next-line no-return-assign
  131. innerRef={(el) => { return this.keymapModeSelectorInputEl = el }}
  132. >
  133. {optionElems}
  134. </Input>
  135. </FormGroup>
  136. );
  137. }
  138. renderConfigurationDropdown() {
  139. return (
  140. <FormGroup className="my-0">
  141. <Dropdown
  142. direction="up"
  143. bsSize="sm"
  144. className="configuration-dropdown"
  145. isOpen={this.state.isCddMenuOpened}
  146. toggle={this.onToggleConfigurationDropdown}
  147. >
  148. <DropdownToggle caret>
  149. <i className="icon-settings"></i>
  150. </DropdownToggle>
  151. <DropdownMenu>
  152. {this.renderActiveLineMenuItem()}
  153. {this.renderRealtimeMathJaxMenuItem()}
  154. {/* <DropdownItem divider /> */}
  155. </DropdownMenu>
  156. </Dropdown>
  157. </FormGroup>
  158. );
  159. }
  160. renderActiveLineMenuItem() {
  161. const { t, editorContainer } = this.props;
  162. const isActive = editorContainer.state.editorOptions.styleActiveLine;
  163. const iconClasses = ['text-info'];
  164. if (isActive) {
  165. iconClasses.push('ti-check');
  166. }
  167. const iconClassName = iconClasses.join(' ');
  168. return (
  169. <DropdownItem toggle={false} onClick={this.onClickStyleActiveLine}>
  170. <span className="icon-container"></span>
  171. <span className="menuitem-label">{ t('page_edit.Show active line') }</span>
  172. <span className="icon-container"><i className={iconClassName}></i></span>
  173. </DropdownItem>
  174. );
  175. }
  176. renderRealtimeMathJaxMenuItem() {
  177. if (!this.state.isMathJaxEnabled) {
  178. return;
  179. }
  180. const { editorContainer } = this.props;
  181. const isEnabled = this.state.isMathJaxEnabled;
  182. const isActive = isEnabled && editorContainer.state.previewOptions.renderMathJaxInRealtime;
  183. const iconClasses = ['text-info'];
  184. if (isActive) {
  185. iconClasses.push('ti-check');
  186. }
  187. const iconClassName = iconClasses.join(' ');
  188. return (
  189. <DropdownItem toggle={false} onClick={this.onClickRenderMathJaxInRealtime}>
  190. <span className="icon-container"><img src="/images/icons/fx.svg" width="14px" alt="fx"></img></span>
  191. <span className="menuitem-label">MathJax Rendering</span>
  192. <i className={iconClassName}></i>
  193. </DropdownItem>
  194. );
  195. }
  196. render() {
  197. return (
  198. <div className="d-flex flex-row">
  199. <span className="m-l-5">{this.renderThemeSelector()}</span>
  200. <span className="m-l-5">{this.renderKeymapModeSelector()}</span>
  201. <span className="m-l-5">{this.renderConfigurationDropdown()}</span>
  202. </div>
  203. );
  204. }
  205. }
  206. /**
  207. * Wrapper component for using unstated
  208. */
  209. const OptionsSelectorWrapper = (props) => {
  210. return createSubscribedElement(OptionsSelector, props, [EditorContainer]);
  211. };
  212. OptionsSelector.propTypes = {
  213. t: PropTypes.func.isRequired, // i18next
  214. editorContainer: PropTypes.instanceOf(EditorContainer).isRequired,
  215. crowi: PropTypes.object.isRequired,
  216. };
  217. export default withTranslation()(OptionsSelectorWrapper);