editor-switch.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  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="badge 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 hasNewSourceEditor: boolean = getMeta('ol-hasNewSourceEditor')
  42. function EditorSwitch() {
  43. const [newSourceEditor, setNewSourceEditor] = useScopeValue(
  44. 'editor.newSourceEditor'
  45. )
  46. const [richText, setRichText] = useScopeValue('editor.showRichText')
  47. const [visual, setVisual] = useScopeValue('editor.showVisual')
  48. const [docName] = useScopeValue('editor.open_doc_name')
  49. const richTextAvailable = isValidTeXFile(docName)
  50. const richTextOrVisual = richText || (richTextAvailable && visual)
  51. const handleChange = useCallback(
  52. event => {
  53. const editorType = event.target.value
  54. switch (editorType) {
  55. case 'ace':
  56. setRichText(false)
  57. setVisual(false)
  58. setNewSourceEditor(false)
  59. break
  60. case 'cm6':
  61. setRichText(false)
  62. setVisual(false)
  63. setNewSourceEditor(true)
  64. break
  65. case 'rich-text':
  66. if (getMeta('ol-richTextVariant') === 'cm6') {
  67. setRichText(false)
  68. setVisual(true)
  69. setNewSourceEditor(true)
  70. } else {
  71. setRichText(true)
  72. setVisual(false)
  73. }
  74. break
  75. }
  76. sendMB('editor-switch-change', { editorType })
  77. },
  78. [setRichText, setVisual, setNewSourceEditor]
  79. )
  80. return (
  81. <div className="editor-toggle-switch">
  82. {showLegacySourceEditor ? <Badge /> : null}
  83. <fieldset className="toggle-switch">
  84. <legend className="sr-only">Editor mode.</legend>
  85. {hasNewSourceEditor && (
  86. <>
  87. <input
  88. type="radio"
  89. name="editor"
  90. value="cm6"
  91. id="editor-switch-cm6"
  92. className="toggle-switch-input"
  93. checked={!richTextOrVisual && !!newSourceEditor}
  94. onChange={handleChange}
  95. />
  96. <label htmlFor="editor-switch-cm6" className="toggle-switch-label">
  97. <span>Source</span>
  98. </label>
  99. </>
  100. )}
  101. {showLegacySourceEditor ? (
  102. <>
  103. <input
  104. type="radio"
  105. name="editor"
  106. value="ace"
  107. id="editor-switch-ace"
  108. className="toggle-switch-input"
  109. checked={!richTextOrVisual && !newSourceEditor}
  110. onChange={handleChange}
  111. />
  112. <label htmlFor="editor-switch-ace" className="toggle-switch-label">
  113. <span>{hasNewSourceEditor ? 'Source (legacy)' : 'Source'}</span>
  114. </label>
  115. </>
  116. ) : null}
  117. <RichTextToggle
  118. checked={!!richTextOrVisual}
  119. disabled={!richTextAvailable}
  120. handleChange={handleChange}
  121. />
  122. </fieldset>
  123. {!!richTextOrVisual && (
  124. <SplitTestBadge splitTestName="rich-text" displayOnVariants={['cm6']} />
  125. )}
  126. </div>
  127. )
  128. }
  129. const RichTextToggle: FC<{
  130. checked: boolean
  131. disabled: boolean
  132. handleChange: (event: ChangeEvent<HTMLInputElement>) => void
  133. }> = ({ checked, disabled, handleChange }) => {
  134. const { t } = useTranslation()
  135. const toggle = (
  136. <span>
  137. <input
  138. type="radio"
  139. name="editor"
  140. value="rich-text"
  141. id="editor-switch-rich-text"
  142. className="toggle-switch-input"
  143. checked={checked}
  144. onChange={handleChange}
  145. disabled={disabled}
  146. />
  147. <label htmlFor="editor-switch-rich-text" className="toggle-switch-label">
  148. <span>Rich Text</span>
  149. </label>
  150. </span>
  151. )
  152. if (disabled) {
  153. return (
  154. <Tooltip
  155. description={t('rich_text_is_only_available_for_tex_files')}
  156. id="rich-text-toggle-tooltip"
  157. overlayProps={{ placement: 'bottom' }}
  158. tooltipProps={{ className: 'tooltip-wide' }}
  159. >
  160. {toggle}
  161. </Tooltip>
  162. )
  163. }
  164. return toggle
  165. }
  166. export default memo(EditorSwitch)