menu-bar.tsx 10 KB

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