change-layout-options.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import {
  2. DropdownItem,
  3. DropdownHeader,
  4. DropdownDivider,
  5. } from '@/shared/components/dropdown/dropdown-menu'
  6. import {
  7. IdeLayout,
  8. IdeView,
  9. useLayoutContext,
  10. } from '@/shared/context/layout-context'
  11. import React from 'react'
  12. import { useTranslation } from 'react-i18next'
  13. import { DetachRole } from '@/shared/context/detach-context'
  14. import OLSpinner from '@/shared/components/ol/ol-spinner'
  15. import { isMac } from '@/shared/utils/os'
  16. import { Shortcut } from '@/shared/components/shortcut'
  17. import classNames from 'classnames'
  18. import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
  19. import { useFeatureFlag } from '@/shared/context/split-test-context'
  20. import SplitTestBadge from '@/shared/components/split-test-badge'
  21. type LayoutOption =
  22. | 'sideBySide'
  23. | 'editorOnly'
  24. | 'pdfOnly'
  25. | 'detachedPdf'
  26. | 'focusMode'
  27. const getActiveLayoutOption = ({
  28. pdfLayout,
  29. view,
  30. detachRole,
  31. }: {
  32. pdfLayout: IdeLayout
  33. view: IdeView | null
  34. detachRole?: DetachRole
  35. }): LayoutOption | null => {
  36. if (view === 'history') {
  37. return null
  38. }
  39. if (detachRole === 'detacher') {
  40. return 'detachedPdf'
  41. }
  42. if (pdfLayout === 'flat' && (view === 'editor' || view === 'file')) {
  43. return 'editorOnly'
  44. }
  45. if (pdfLayout === 'flat' && view === 'pdf') {
  46. return 'pdfOnly'
  47. }
  48. if (pdfLayout === 'sideBySide') {
  49. return 'sideBySide'
  50. }
  51. return null
  52. }
  53. const LayoutDropdownItem = ({
  54. active,
  55. disabled = false,
  56. processing = false,
  57. leadingIcon,
  58. trailingIcon,
  59. onClick,
  60. children,
  61. }: {
  62. active: boolean
  63. leadingIcon: React.ReactNode
  64. trailingIcon?: React.ReactNode
  65. onClick: () => void
  66. children: React.ReactNode
  67. processing?: boolean
  68. disabled?: boolean
  69. }) => {
  70. if (processing) {
  71. leadingIcon = <OLSpinner size="sm" />
  72. } else if (active) {
  73. leadingIcon = 'check'
  74. }
  75. return (
  76. <DropdownItem
  77. active={active}
  78. aria-current={active}
  79. disabled={disabled}
  80. onClick={onClick}
  81. leadingIcon={leadingIcon}
  82. trailingIcon={trailingIcon}
  83. className={classNames({ 'dropdown-item-wide': isMac })}
  84. >
  85. {children}
  86. </DropdownItem>
  87. )
  88. }
  89. const shortcuts: Record<LayoutOption, string[] | null> = isMac
  90. ? {
  91. editorOnly: ['⌃', '⌘', '←'],
  92. pdfOnly: ['⌃', '⌘', '→'],
  93. sideBySide: ['⌃', '⌘', '↓'],
  94. detachedPdf: ['⌃', '⌘', '↑'],
  95. focusMode: ['⌘', '⇧', 'M'],
  96. }
  97. : {
  98. editorOnly: null,
  99. pdfOnly: null,
  100. sideBySide: null,
  101. detachedPdf: null,
  102. focusMode: ['⌃', '⇧', 'M'],
  103. }
  104. export default function ChangeLayoutOptions() {
  105. const {
  106. detachIsLinked,
  107. detachRole,
  108. view,
  109. pdfLayout,
  110. handleChangeLayout,
  111. handleDetach,
  112. focusMode,
  113. setFocusMode,
  114. } = useLayoutContext()
  115. const { t } = useTranslation()
  116. const { sendEvent } = useEditorAnalytics()
  117. const focusModeEnabled = useFeatureFlag('focus-mode')
  118. const detachable = 'BroadcastChannel' in window
  119. const activeLayoutOption = getActiveLayoutOption({
  120. pdfLayout,
  121. view,
  122. detachRole,
  123. })
  124. const waitingForDetachedLink = !detachIsLinked && detachRole === 'detacher'
  125. return (
  126. <>
  127. <DropdownHeader>{t('layout_options')}</DropdownHeader>
  128. <LayoutDropdownItem
  129. onClick={() => handleChangeLayout('sideBySide')}
  130. active={activeLayoutOption === 'sideBySide'}
  131. leadingIcon="splitscreen_right"
  132. trailingIcon={
  133. shortcuts.sideBySide && <Shortcut keys={shortcuts.sideBySide} />
  134. }
  135. >
  136. {t('split_view')}
  137. </LayoutDropdownItem>
  138. <LayoutDropdownItem
  139. onClick={() => handleChangeLayout('flat', 'editor')}
  140. active={activeLayoutOption === 'editorOnly'}
  141. leadingIcon="edit"
  142. trailingIcon={
  143. shortcuts.editorOnly && <Shortcut keys={shortcuts.editorOnly} />
  144. }
  145. >
  146. {t('editor_only')}
  147. </LayoutDropdownItem>
  148. <LayoutDropdownItem
  149. onClick={() => handleChangeLayout('flat', 'pdf')}
  150. active={activeLayoutOption === 'pdfOnly'}
  151. leadingIcon="picture_as_pdf"
  152. trailingIcon={
  153. shortcuts.pdfOnly && <Shortcut keys={shortcuts.pdfOnly} />
  154. }
  155. >
  156. {t('pdf_only')}
  157. </LayoutDropdownItem>
  158. <LayoutDropdownItem
  159. onClick={() => handleDetach()}
  160. active={activeLayoutOption === 'detachedPdf' && detachIsLinked}
  161. disabled={!detachable}
  162. leadingIcon="open_in_new"
  163. trailingIcon={
  164. shortcuts.detachedPdf && <Shortcut keys={shortcuts.detachedPdf} />
  165. }
  166. processing={waitingForDetachedLink}
  167. >
  168. {t('open_pdf_in_separate_tab')}
  169. </LayoutDropdownItem>
  170. {focusModeEnabled && (
  171. <>
  172. <DropdownDivider />
  173. <LayoutDropdownItem
  174. onClick={() => {
  175. setFocusMode(!focusMode)
  176. sendEvent('focus-mode-toggle', { focusMode: !focusMode })
  177. }}
  178. active={focusMode}
  179. leadingIcon="crop_free"
  180. trailingIcon={
  181. <span className="d-flex align-items-center gap-2">
  182. <SplitTestBadge
  183. splitTestName="focus-mode"
  184. displayOnVariants={['enabled']}
  185. />
  186. {shortcuts.focusMode && <Shortcut keys={shortcuts.focusMode} />}
  187. </span>
  188. }
  189. >
  190. {t('focus_mode')}
  191. </LayoutDropdownItem>
  192. </>
  193. )}
  194. </>
  195. )
  196. }