change-layout-options.tsx 4.1 KB

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