editor-switch.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import { ChangeEvent, FC, memo, useCallback } from 'react'
  2. import useScopeValue from '../../../shared/hooks/use-scope-value'
  3. import Tooltip from '../../../shared/components/tooltip'
  4. import { sendMB } from '../../../infrastructure/event-tracking'
  5. import getMeta from '../../../utils/meta'
  6. import SplitTestBadge from '../../../shared/components/split-test-badge'
  7. import isValidTeXFile from '../../../main/is-valid-tex-file'
  8. import { useTranslation } from 'react-i18next'
  9. function Badge() {
  10. const content = (
  11. <>
  12. Overleaf has upgraded the source editor. You can still use the old editor
  13. by selecting "Source (legacy)".
  14. <br />
  15. <br />
  16. Click to learn more and give feedback
  17. </>
  18. )
  19. return (
  20. <Tooltip
  21. id="editor-switch"
  22. description={content}
  23. overlayProps={{
  24. placement: 'bottom',
  25. delayHide: 100,
  26. }}
  27. tooltipProps={{ className: 'tooltip-wide' }}
  28. >
  29. <a
  30. href="https://forms.gle/GmSs6odZRKRp3VX98"
  31. target="_blank"
  32. rel="noopener noreferrer"
  33. className="info-badge"
  34. >
  35. <span className="sr-only">{content}</span>
  36. </a>
  37. </Tooltip>
  38. )
  39. }
  40. const showLegacySourceEditor: boolean = getMeta('ol-showLegacySourceEditor')
  41. const visualEditorNameVariant: string = getMeta('ol-visualEditorNameVariant')
  42. const isParticipatingInVisualEditorNamingTest: boolean = getMeta(
  43. 'ol-isParticipatingInVisualEditorNamingTest'
  44. )
  45. function EditorSwitch() {
  46. const [newSourceEditor, setNewSourceEditor] = useScopeValue(
  47. 'editor.newSourceEditor'
  48. )
  49. const [richText, setRichText] = useScopeValue('editor.showRichText')
  50. const sourceName =
  51. visualEditorNameVariant === 'code-visual'
  52. ? 'Code Editor'
  53. : visualEditorNameVariant === 'source-visual'
  54. ? 'Source Editor'
  55. : 'Source'
  56. const [visual, setVisual] = useScopeValue('editor.showVisual')
  57. const [docName] = useScopeValue('editor.open_doc_name')
  58. const richTextAvailable = isValidTeXFile(docName)
  59. const richTextOrVisual = richText || (richTextAvailable && visual)
  60. const handleChange = useCallback(
  61. event => {
  62. const editorType = event.target.value
  63. switch (editorType) {
  64. case 'ace':
  65. setRichText(false)
  66. setVisual(false)
  67. setNewSourceEditor(false)
  68. break
  69. case 'cm6':
  70. setRichText(false)
  71. setVisual(false)
  72. setNewSourceEditor(true)
  73. break
  74. case 'rich-text':
  75. if (getMeta('ol-richTextVariant') === 'cm6') {
  76. setRichText(false)
  77. setVisual(true)
  78. setNewSourceEditor(true)
  79. } else {
  80. setRichText(true)
  81. setVisual(false)
  82. }
  83. break
  84. }
  85. sendMB('editor-switch-change', { editorType })
  86. },
  87. [setRichText, setVisual, setNewSourceEditor]
  88. )
  89. return (
  90. <div className="editor-toggle-switch">
  91. {showLegacySourceEditor ? <Badge /> : null}
  92. <fieldset className="toggle-switch">
  93. <legend className="sr-only">Editor mode.</legend>
  94. <input
  95. type="radio"
  96. name="editor"
  97. value="cm6"
  98. id="editor-switch-cm6"
  99. className="toggle-switch-input"
  100. checked={!richTextOrVisual && !!newSourceEditor}
  101. onChange={handleChange}
  102. />
  103. <label htmlFor="editor-switch-cm6" className="toggle-switch-label">
  104. <span>{sourceName}</span>
  105. </label>
  106. {showLegacySourceEditor ? (
  107. <>
  108. <input
  109. type="radio"
  110. name="editor"
  111. value="ace"
  112. id="editor-switch-ace"
  113. className="toggle-switch-input"
  114. checked={!richTextOrVisual && !newSourceEditor}
  115. onChange={handleChange}
  116. />
  117. <label htmlFor="editor-switch-ace" className="toggle-switch-label">
  118. <span>Source (legacy)</span>
  119. </label>
  120. </>
  121. ) : null}
  122. <RichTextToggle
  123. checked={!!richTextOrVisual}
  124. disabled={!richTextAvailable}
  125. handleChange={handleChange}
  126. />
  127. </fieldset>
  128. {!!richTextOrVisual && !isParticipatingInVisualEditorNamingTest && (
  129. <SplitTestBadge splitTestName="rich-text" displayOnVariants={['cm6']} />
  130. )}
  131. </div>
  132. )
  133. }
  134. const RichTextToggle: FC<{
  135. checked: boolean
  136. disabled: boolean
  137. handleChange: (event: ChangeEvent<HTMLInputElement>) => void
  138. }> = ({ checked, disabled, handleChange }) => {
  139. const { t } = useTranslation()
  140. const richTextName =
  141. visualEditorNameVariant === 'default' ? 'Rich Text' : 'Visual Editor'
  142. const toggle = (
  143. <span>
  144. <input
  145. type="radio"
  146. name="editor"
  147. value="rich-text"
  148. id="editor-switch-rich-text"
  149. className="toggle-switch-input"
  150. checked={checked}
  151. onChange={handleChange}
  152. disabled={disabled}
  153. />
  154. <label htmlFor="editor-switch-rich-text" className="toggle-switch-label">
  155. <span>{richTextName}</span>
  156. </label>
  157. </span>
  158. )
  159. if (disabled) {
  160. return (
  161. <Tooltip
  162. description={t('rich_text_is_only_available_for_tex_files')}
  163. id="rich-text-toggle-tooltip"
  164. overlayProps={{ placement: 'bottom' }}
  165. tooltipProps={{ className: 'tooltip-wide' }}
  166. >
  167. {toggle}
  168. </Tooltip>
  169. )
  170. }
  171. return toggle
  172. }
  173. export default memo(EditorSwitch)