menu-bar.tsx 9.9 KB

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