change-layout-options.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import {
  2. DropdownItem,
  3. DropdownHeader,
  4. } from '@/shared/components/dropdown/dropdown-menu'
  5. import {
  6. IdeLayout,
  7. IdeView,
  8. useLayoutContext,
  9. } from '@/shared/context/layout-context'
  10. import React from 'react'
  11. import { useTranslation } from 'react-i18next'
  12. import { DetachRole } from '@/shared/context/detach-context'
  13. import OLSpinner from '@/shared/components/ol/ol-spinner'
  14. import { isMac } from '@/shared/utils/os'
  15. import { Shortcut } from '@/shared/components/shortcut'
  16. import classNames from 'classnames'
  17. type LayoutOption = 'sideBySide' | 'editorOnly' | 'pdfOnly' | 'detachedPdf'
  18. const getActiveLayoutOption = ({
  19. pdfLayout,
  20. view,
  21. detachRole,
  22. }: {
  23. pdfLayout: IdeLayout
  24. view: IdeView | null
  25. detachRole?: DetachRole
  26. }): LayoutOption | null => {
  27. if (view === 'history') {
  28. return null
  29. }
  30. if (detachRole === 'detacher') {
  31. return 'detachedPdf'
  32. }
  33. if (pdfLayout === 'flat' && (view === 'editor' || view === 'file')) {
  34. return 'editorOnly'
  35. }
  36. if (pdfLayout === 'flat' && view === 'pdf') {
  37. return 'pdfOnly'
  38. }
  39. if (pdfLayout === 'sideBySide') {
  40. return 'sideBySide'
  41. }
  42. return null
  43. }
  44. const LayoutDropdownItem = ({
  45. active,
  46. disabled = false,
  47. processing = false,
  48. leadingIcon,
  49. trailingIcon,
  50. onClick,
  51. children,
  52. }: {
  53. active: boolean
  54. leadingIcon: React.ReactNode
  55. trailingIcon?: React.ReactNode
  56. onClick: () => void
  57. children: React.ReactNode
  58. processing?: boolean
  59. disabled?: boolean
  60. }) => {
  61. if (processing) {
  62. leadingIcon = <OLSpinner size="sm" />
  63. } else if (active) {
  64. leadingIcon = 'check'
  65. }
  66. return (
  67. <DropdownItem
  68. active={active}
  69. aria-current={active}
  70. disabled={disabled}
  71. onClick={onClick}
  72. leadingIcon={leadingIcon}
  73. trailingIcon={trailingIcon}
  74. className={classNames({ 'dropdown-item-wide': isMac })}
  75. >
  76. {children}
  77. </DropdownItem>
  78. )
  79. }
  80. const shortcuts: Record<LayoutOption, string[] | null> = isMac
  81. ? {
  82. editorOnly: ['⌃', '⌘', '←'],
  83. pdfOnly: ['⌃', '⌘', '→'],
  84. sideBySide: ['⌃', '⌘', '↓'],
  85. detachedPdf: ['⌃', '⌘', '↑'],
  86. }
  87. : {
  88. editorOnly: null,
  89. pdfOnly: null,
  90. sideBySide: null,
  91. detachedPdf: null,
  92. }
  93. export default function ChangeLayoutOptions() {
  94. const {
  95. detachIsLinked,
  96. detachRole,
  97. view,
  98. pdfLayout,
  99. handleChangeLayout,
  100. handleDetach,
  101. } = useLayoutContext()
  102. const { t } = useTranslation()
  103. const detachable = 'BroadcastChannel' in window
  104. const activeLayoutOption = getActiveLayoutOption({
  105. pdfLayout,
  106. view,
  107. detachRole,
  108. })
  109. const waitingForDetachedLink = !detachIsLinked && detachRole === 'detacher'
  110. return (
  111. <>
  112. <DropdownHeader>{t('layout_options')}</DropdownHeader>
  113. <LayoutDropdownItem
  114. onClick={() => handleChangeLayout('sideBySide')}
  115. active={activeLayoutOption === 'sideBySide'}
  116. leadingIcon="splitscreen_right"
  117. trailingIcon={
  118. shortcuts.sideBySide && <Shortcut keys={shortcuts.sideBySide} />
  119. }
  120. >
  121. {t('split_view')}
  122. </LayoutDropdownItem>
  123. <LayoutDropdownItem
  124. onClick={() => handleChangeLayout('flat', 'editor')}
  125. active={activeLayoutOption === 'editorOnly'}
  126. leadingIcon="edit"
  127. trailingIcon={
  128. shortcuts.editorOnly && <Shortcut keys={shortcuts.editorOnly} />
  129. }
  130. >
  131. {t('editor_only')}
  132. </LayoutDropdownItem>
  133. <LayoutDropdownItem
  134. onClick={() => handleChangeLayout('flat', 'pdf')}
  135. active={activeLayoutOption === 'pdfOnly'}
  136. leadingIcon="picture_as_pdf"
  137. trailingIcon={
  138. shortcuts.pdfOnly && <Shortcut keys={shortcuts.pdfOnly} />
  139. }
  140. >
  141. {t('pdf_only')}
  142. </LayoutDropdownItem>
  143. <LayoutDropdownItem
  144. onClick={() => handleDetach()}
  145. active={activeLayoutOption === 'detachedPdf' && detachIsLinked}
  146. disabled={!detachable}
  147. leadingIcon="open_in_new"
  148. trailingIcon={
  149. shortcuts.detachedPdf && <Shortcut keys={shortcuts.detachedPdf} />
  150. }
  151. processing={waitingForDetachedLink}
  152. >
  153. {t('open_pdf_in_separate_tab')}
  154. </LayoutDropdownItem>
  155. </>
  156. )
  157. }