OptionsSelector.tsx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. import React, {
  2. memo, useCallback, useMemo, useState,
  3. } from 'react';
  4. import { useTranslation } from 'next-i18next';
  5. import {
  6. Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
  7. } from 'reactstrap';
  8. import { useIsIndentSizeForced } from '~/stores/context';
  9. import { useEditorSettings, useCurrentIndentSize } from '~/stores/editor';
  10. import { DEFAULT_THEME, KeyMapMode } from '../../interfaces/editor-settings';
  11. const AVAILABLE_THEMES = [
  12. 'DefaultLight', 'Eclipse', 'Basic', 'Ayu', 'Rosé Pine', 'DefaultDark', 'Material', 'Nord', 'Cobalt', 'Kimbie',
  13. ];
  14. const TYPICAL_INDENT_SIZE = [2, 4];
  15. const ThemeSelector = (): JSX.Element => {
  16. const [isThemeMenuOpened, setIsThemeMenuOpened] = useState(false);
  17. const { data: editorSettings, update } = useEditorSettings();
  18. const menuItems = useMemo(() => (
  19. <>
  20. { AVAILABLE_THEMES.map((theme) => {
  21. return (
  22. <DropdownItem className="menuitem-label" onClick={() => update({ theme })}>
  23. {theme}
  24. </DropdownItem>
  25. );
  26. }) }
  27. </>
  28. ), [update]);
  29. const selectedTheme = editorSettings?.theme ?? DEFAULT_THEME;
  30. return (
  31. <div className="input-group flex-nowrap">
  32. <div>
  33. <span className="input-group-text" id="igt-theme">Theme</span>
  34. </div>
  35. <Dropdown
  36. direction="up"
  37. isOpen={isThemeMenuOpened}
  38. toggle={() => setIsThemeMenuOpened(!isThemeMenuOpened)}
  39. >
  40. <DropdownToggle color="outline-secondary" caret>
  41. {selectedTheme}
  42. </DropdownToggle>
  43. <DropdownMenu container="body">
  44. {menuItems}
  45. </DropdownMenu>
  46. </Dropdown>
  47. </div>
  48. );
  49. };
  50. type KeyMapModeToLabel = {
  51. [key in KeyMapMode]: string;
  52. }
  53. const KEYMAP_LABEL_MAP: KeyMapModeToLabel = {
  54. default: 'Default',
  55. vim: 'Vim',
  56. emacs: 'Emacs',
  57. sublime: 'Sublime Text',
  58. };
  59. const KeymapSelector = memo((): JSX.Element => {
  60. const [isKeyMenuOpened, setIsKeyMenuOpened] = useState(false);
  61. const { data: editorSettings, update } = useEditorSettings();
  62. const menuItems = useMemo(() => (
  63. <>
  64. { (Object.keys(KEYMAP_LABEL_MAP) as KeyMapMode[]).map((keymapMode) => {
  65. const keymapLabel = KEYMAP_LABEL_MAP[keymapMode];
  66. const icon = (keymapMode !== 'default')
  67. ? <img src={`/images/icons/${keymapMode}.png`} width="16px" className="me-2"></img>
  68. : null;
  69. return (
  70. <DropdownItem className="menuitem-label" onClick={() => update({ keymapMode })}>
  71. {icon}{keymapLabel}
  72. </DropdownItem>
  73. );
  74. }) }
  75. </>
  76. ), [update]);
  77. const selectedKeymapMode = editorSettings?.keymapMode ?? 'default';
  78. return (
  79. <div className="input-group flex-nowrap">
  80. <span className="input-group-text" id="igt-keymap">Keymap</span>
  81. <Dropdown
  82. direction="up"
  83. isOpen={isKeyMenuOpened}
  84. toggle={() => setIsKeyMenuOpened(!isKeyMenuOpened)}
  85. >
  86. <DropdownToggle color="outline-secondary" caret>
  87. {selectedKeymapMode}
  88. </DropdownToggle>
  89. <DropdownMenu container="body">
  90. {menuItems}
  91. </DropdownMenu>
  92. </Dropdown>
  93. </div>
  94. );
  95. });
  96. KeymapSelector.displayName = 'KeymapSelector';
  97. type IndentSizeSelectorProps = {
  98. isIndentSizeForced: boolean,
  99. selectedIndentSize: number,
  100. onChange: (indentSize: number) => void,
  101. }
  102. const IndentSizeSelector = memo(({ isIndentSizeForced, selectedIndentSize, onChange }: IndentSizeSelectorProps): JSX.Element => {
  103. const [isIndentMenuOpened, setIsIndentMenuOpened] = useState(false);
  104. const menuItems = useMemo(() => (
  105. <>
  106. { TYPICAL_INDENT_SIZE.map((indent) => {
  107. return (
  108. <DropdownItem className="menuitem-label" onClick={() => onChange(indent)}>
  109. {indent}
  110. </DropdownItem>
  111. );
  112. }) }
  113. </>
  114. ), [onChange]);
  115. return (
  116. <div className="input-group flex-nowrap">
  117. <span className="input-group-text" id="igt-indent">Indent</span>
  118. <Dropdown
  119. direction="up"
  120. isOpen={isIndentMenuOpened}
  121. toggle={() => setIsIndentMenuOpened(!isIndentMenuOpened)}
  122. disabled={isIndentSizeForced}
  123. >
  124. <DropdownToggle color="outline-secondary" caret>
  125. {selectedIndentSize}
  126. </DropdownToggle>
  127. <DropdownMenu container="body">
  128. {menuItems}
  129. </DropdownMenu>
  130. </Dropdown>
  131. </div>
  132. );
  133. });
  134. IndentSizeSelector.displayName = 'IndentSizeSelector';
  135. const ConfigurationDropdown = memo((): JSX.Element => {
  136. const { t } = useTranslation();
  137. const [isCddMenuOpened, setCddMenuOpened] = useState(false);
  138. const { data: editorSettings, update } = useEditorSettings();
  139. const renderActiveLineMenuItem = useCallback(() => {
  140. if (editorSettings == null) {
  141. return <></>;
  142. }
  143. const isActive = editorSettings.styleActiveLine;
  144. const iconClasses = ['text-info'];
  145. if (isActive) {
  146. iconClasses.push('ti ti-check');
  147. }
  148. const iconClassName = iconClasses.join(' ');
  149. return (
  150. <DropdownItem toggle={false} onClick={() => update({ styleActiveLine: !isActive })}>
  151. <div className="d-flex justify-content-between">
  152. <span className="icon-container"></span>
  153. <span className="menuitem-label">{ t('page_edit.Show active line') }</span>
  154. <span className="icon-container"><i className={iconClassName}></i></span>
  155. </div>
  156. </DropdownItem>
  157. );
  158. }, [editorSettings, update, t]);
  159. const renderMarkdownTableAutoFormattingMenuItem = useCallback(() => {
  160. if (editorSettings == null) {
  161. return <></>;
  162. }
  163. const isActive = editorSettings.autoFormatMarkdownTable;
  164. const iconClasses = ['text-info'];
  165. if (isActive) {
  166. iconClasses.push('ti ti-check');
  167. }
  168. const iconClassName = iconClasses.join(' ');
  169. return (
  170. <DropdownItem toggle={false} onClick={() => update({ autoFormatMarkdownTable: !isActive })}>
  171. <div className="d-flex justify-content-between">
  172. <span className="icon-container"></span>
  173. <span className="menuitem-label">{ t('page_edit.auto_format_table') }</span>
  174. <span className="icon-container"><i className={iconClassName}></i></span>
  175. </div>
  176. </DropdownItem>
  177. );
  178. }, [editorSettings, t, update]);
  179. return (
  180. <div className="my-0">
  181. <Dropdown
  182. direction="up"
  183. className="grw-editor-configuration-dropdown"
  184. isOpen={isCddMenuOpened}
  185. toggle={() => setCddMenuOpened(!isCddMenuOpened)}
  186. >
  187. <DropdownToggle color="outline-secondary" caret>
  188. <i className="icon-settings"></i>
  189. </DropdownToggle>
  190. <DropdownMenu container="body">
  191. {renderActiveLineMenuItem()}
  192. {renderMarkdownTableAutoFormattingMenuItem()}
  193. {/* <DropdownItem divider /> */}
  194. </DropdownMenu>
  195. </Dropdown>
  196. </div>
  197. );
  198. });
  199. ConfigurationDropdown.displayName = 'ConfigurationDropdown';
  200. export const OptionsSelector = (): JSX.Element => {
  201. const { data: editorSettings } = useEditorSettings();
  202. const { data: isIndentSizeForced } = useIsIndentSizeForced();
  203. const { data: currentIndentSize, mutate: mutateCurrentIndentSize } = useCurrentIndentSize();
  204. if (editorSettings == null || isIndentSizeForced == null || currentIndentSize == null) {
  205. return <></>;
  206. }
  207. return (
  208. <>
  209. <div className="d-flex flex-row zindex-dropdown">
  210. <span>
  211. <ThemeSelector />
  212. </span>
  213. <span className="d-none d-sm-block ms-2 ms-sm-4">
  214. <KeymapSelector />
  215. </span>
  216. <span className="ms-2 ms-sm-4">
  217. <IndentSizeSelector
  218. isIndentSizeForced={isIndentSizeForced}
  219. selectedIndentSize={currentIndentSize}
  220. onChange={newValue => mutateCurrentIndentSize(newValue)}
  221. />
  222. </span>
  223. <span className="ms-2 ms-sm-4">
  224. <ConfigurationDropdown />
  225. </span>
  226. </div>
  227. </>
  228. );
  229. };