menu-bar.tsx 10 KB

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