menu-bar.tsx 9.3 KB

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