menu-bar.tsx 8.8 KB

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