menu-bar.tsx 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. import {
  2. DropdownDivider,
  3. DropdownHeader,
  4. DropdownItem,
  5. } from '@/features/ui/components/bootstrap-5/dropdown-menu'
  6. import { MenuBar } from '@/shared/components/menu-bar/menu-bar'
  7. import { MenuBarDropdown } from '@/shared/components/menu-bar/menu-bar-dropdown'
  8. import { MenuBarOption } from '@/shared/components/menu-bar/menu-bar-option'
  9. import { useTranslation } from 'react-i18next'
  10. import ChangeLayoutOptions from './change-layout-options'
  11. import { MouseEventHandler, useCallback, useMemo, useState } from 'react'
  12. import { useIdeRedesignSwitcherContext } from '@/features/ide-react/context/ide-redesign-switcher-context'
  13. import { useSwitchEnableNewEditorState } from '../../hooks/use-switch-enable-new-editor-state'
  14. import MaterialIcon from '@/shared/components/material-icon'
  15. import OLSpinner from '@/features/ui/components/ol/ol-spinner'
  16. import { useLayoutContext } from '@/shared/context/layout-context'
  17. import { useCommandProvider } from '@/features/ide-react/hooks/use-command-provider'
  18. import CommandDropdown, {
  19. CommandSection,
  20. MenuSectionStructure,
  21. MenuStructure,
  22. } from './command-dropdown'
  23. import { useRailContext } from '../../contexts/rail-context'
  24. import WordCountModal from '@/features/word-count-modal/components/word-count-modal'
  25. import { isSplitTestEnabled } from '@/utils/splitTestUtils'
  26. import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context'
  27. import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
  28. import { useProjectSettingsContext } from '@/features/editor-left-menu/context/project-settings-context'
  29. import { useSurveyUrl } from '../../hooks/use-survey-url'
  30. export const ToolbarMenuBar = () => {
  31. const { t } = useTranslation()
  32. const { setShowSwitcherModal } = useIdeRedesignSwitcherContext()
  33. const openEditorRedesignSwitcherModal = useCallback(() => {
  34. setShowSwitcherModal(true)
  35. }, [setShowSwitcherModal])
  36. const { setView, view } = useLayoutContext()
  37. const { pdfUrl } = useCompileContext()
  38. const wordCountEnabled = pdfUrl || isSplitTestEnabled('word-count-client')
  39. const [showWordCountModal, setShowWordCountModal] = useState(false)
  40. useCommandProvider(
  41. () => [
  42. {
  43. type: 'command',
  44. label: t('show_version_history'),
  45. handler: () => {
  46. setView(view === 'history' ? 'editor' : 'history')
  47. },
  48. id: 'show_version_history',
  49. },
  50. {
  51. type: 'command',
  52. label: t('word_count_lower'),
  53. disabled: !wordCountEnabled,
  54. handler: () => {
  55. setShowWordCountModal(true)
  56. },
  57. id: 'word_count',
  58. },
  59. ],
  60. [t, setView, view, wordCountEnabled]
  61. )
  62. const fileMenuStructure: MenuStructure = useMemo(
  63. () => [
  64. {
  65. id: 'file-file-tree',
  66. children: ['new_file', 'new_folder', 'upload_file'],
  67. },
  68. { id: 'file-tools', children: ['show_version_history', 'word_count'] },
  69. {
  70. id: 'file-download',
  71. children: ['download-as-source-zip', 'download-pdf'],
  72. },
  73. ],
  74. []
  75. )
  76. const editMenuStructure: MenuStructure = useMemo(
  77. () => [
  78. {
  79. id: 'edit-undo-redo',
  80. children: ['undo', 'redo'],
  81. },
  82. {
  83. id: 'edit-search',
  84. children: ['find', 'select-all'],
  85. },
  86. ],
  87. []
  88. )
  89. const insertMenuStructure: MenuStructure = useMemo(
  90. () => [
  91. {
  92. id: 'insert-latex',
  93. children: [
  94. {
  95. id: 'insert-math-group',
  96. title: t('math'),
  97. children: ['insert-inline-math', 'insert-display-math'],
  98. },
  99. 'insert-symbol',
  100. {
  101. id: 'insert-figure-group',
  102. title: t('figure'),
  103. children: [
  104. 'insert-figure-from-computer',
  105. 'insert-figure-from-project-files',
  106. 'insert-figure-from-another-project',
  107. 'insert-figure-from-url',
  108. ],
  109. },
  110. 'insert-table',
  111. 'insert-citation',
  112. 'insert-link',
  113. 'insert-cross-reference',
  114. ],
  115. },
  116. {
  117. id: 'insert-comment',
  118. children: ['comment'],
  119. },
  120. ],
  121. [t]
  122. )
  123. const formatMenuStructure: MenuStructure = useMemo(
  124. () => [
  125. {
  126. id: 'format-text',
  127. children: ['format-bold', 'format-italics'],
  128. },
  129. {
  130. id: 'format-list',
  131. children: [
  132. 'format-bullet-list',
  133. 'format-numbered-list',
  134. 'format-increase-indentation',
  135. 'format-decrease-indentation',
  136. ],
  137. },
  138. {
  139. id: 'format-paragraph',
  140. title: t('paragraph_styles'),
  141. children: [
  142. 'format-style-normal',
  143. 'format-style-section',
  144. 'format-style-subsection',
  145. 'format-style-subsubsection',
  146. 'format-style-paragraph',
  147. 'format-style-subparagraph',
  148. ],
  149. },
  150. ],
  151. [t]
  152. )
  153. const pdfControlsMenuSectionStructure: MenuSectionStructure = useMemo(
  154. () => ({
  155. title: t('pdf_preview'),
  156. id: 'pdf-controls',
  157. children: [
  158. 'view-pdf-presentation-mode',
  159. 'view-pdf-zoom-in',
  160. 'view-pdf-zoom-out',
  161. 'view-pdf-fit-width',
  162. 'view-pdf-fit-height',
  163. ],
  164. }),
  165. [t]
  166. )
  167. const { mathPreview, setMathPreview, breadcrumbs, setBreadcrumbs } =
  168. useProjectSettingsContext()
  169. const toggleMathPreview = useCallback(() => {
  170. setMathPreview(!mathPreview)
  171. }, [setMathPreview, mathPreview])
  172. const toggleBreadcrumbs = useCallback(() => {
  173. setBreadcrumbs(!breadcrumbs)
  174. }, [setBreadcrumbs, breadcrumbs])
  175. const { setActiveModal } = useRailContext()
  176. const openKeyboardShortcutsModal = useCallback(() => {
  177. setActiveModal('keyboard-shortcuts')
  178. }, [setActiveModal])
  179. const openContactUsModal = useCallback(() => {
  180. setActiveModal('contact-us')
  181. }, [setActiveModal])
  182. const surveyURL = useSurveyUrl()
  183. return (
  184. <>
  185. <MenuBar
  186. className="ide-redesign-toolbar-menu-bar"
  187. id="toolbar-menu-bar-item"
  188. >
  189. <CommandDropdown menu={fileMenuStructure} title={t('file')} id="file" />
  190. <CommandDropdown menu={editMenuStructure} title={t('edit')} id="edit" />
  191. <CommandDropdown
  192. menu={insertMenuStructure}
  193. title={t('insert')}
  194. id="insert"
  195. />
  196. <MenuBarDropdown
  197. title={t('view')}
  198. id="view"
  199. className="ide-redesign-toolbar-dropdown-toggle-subdued ide-redesign-toolbar-button-subdued"
  200. >
  201. <ChangeLayoutOptions />
  202. <DropdownDivider />
  203. <DropdownHeader>Editor settings</DropdownHeader>
  204. <MenuBarOption
  205. eventKey="show_breadcrumbs"
  206. title={t('show_breadcrumbs')}
  207. leadingIcon={
  208. breadcrumbs ? 'check' : <DropdownItem.EmptyLeadingIcon />
  209. }
  210. onClick={toggleBreadcrumbs}
  211. />
  212. <MenuBarOption
  213. eventKey="show_equation_preview"
  214. title={t('show_equation_preview')}
  215. leadingIcon={
  216. mathPreview ? 'check' : <DropdownItem.EmptyLeadingIcon />
  217. }
  218. onClick={toggleMathPreview}
  219. />
  220. <DropdownDivider />
  221. <CommandSection section={pdfControlsMenuSectionStructure} />
  222. </MenuBarDropdown>
  223. <CommandDropdown
  224. menu={formatMenuStructure}
  225. title={t('format')}
  226. id="format"
  227. />
  228. <MenuBarDropdown
  229. title={t('help')}
  230. id="help"
  231. className="ide-redesign-toolbar-dropdown-toggle-subdued ide-redesign-toolbar-button-subdued"
  232. >
  233. <MenuBarOption
  234. eventKey="keyboard_shortcuts"
  235. title={t('keyboard_shortcuts')}
  236. onClick={openKeyboardShortcutsModal}
  237. />
  238. <MenuBarOption
  239. title={t('documentation')}
  240. eventKey="documentation"
  241. href="/learn"
  242. target="_blank"
  243. rel="noopener noreferrer"
  244. />
  245. <DropdownDivider />
  246. <MenuBarOption
  247. eventKey="contact_us"
  248. title={t('contact_us')}
  249. onClick={openContactUsModal}
  250. />
  251. <MenuBarOption
  252. eventKey="give_feedback"
  253. title={t('give_feedback')}
  254. href={surveyURL}
  255. target="_blank"
  256. rel="noopener noreferrer"
  257. />
  258. <DropdownDivider />
  259. <SwitchToOldEditorMenuBarOption />
  260. <MenuBarOption
  261. eventKey="whats_new"
  262. title="What's new?"
  263. onClick={openEditorRedesignSwitcherModal}
  264. />
  265. </MenuBarDropdown>
  266. </MenuBar>
  267. <WordCountModal
  268. show={showWordCountModal}
  269. handleHide={() => setShowWordCountModal(false)}
  270. />
  271. </>
  272. )
  273. }
  274. const SwitchToOldEditorMenuBarOption = () => {
  275. const { loading, error, setEditorRedesignStatus } =
  276. useSwitchEnableNewEditorState()
  277. const { sendEvent } = useEditorAnalytics()
  278. const disable: MouseEventHandler = useCallback(
  279. event => {
  280. // Don't close the dropdown
  281. event.stopPropagation()
  282. sendEvent('editor-redesign-toggle', {
  283. action: 'disable',
  284. location: 'menu-bar',
  285. })
  286. setEditorRedesignStatus(false)
  287. },
  288. [setEditorRedesignStatus, sendEvent]
  289. )
  290. let icon = null
  291. if (loading) {
  292. icon = <OLSpinner size="sm" />
  293. } else if (error) {
  294. icon = <MaterialIcon type="error" title={error} className="text-danger" />
  295. }
  296. return (
  297. <MenuBarOption
  298. eventKey="switch_to_old_editor"
  299. title="Switch to old editor"
  300. onClick={disable}
  301. disabled={loading}
  302. trailingIcon={icon}
  303. />
  304. )
  305. }