layout-dropdown-button.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import { useCallback } from 'react'
  2. import PropTypes from 'prop-types'
  3. import { Dropdown, MenuItem, OverlayTrigger, Tooltip } from 'react-bootstrap'
  4. import { Trans, useTranslation } from 'react-i18next'
  5. import Icon from '../../../shared/components/icon'
  6. import IconChecked from '../../../shared/components/icon-checked'
  7. import ControlledDropdown from '../../../shared/components/controlled-dropdown'
  8. import IconEditorOnly from './icon-editor-only'
  9. import IconPdfOnly from './icon-pdf-only'
  10. import { useLayoutContext } from '../../../shared/context/layout-context'
  11. import * as eventTracking from '../../../infrastructure/event-tracking'
  12. function IconPlaceholder() {
  13. return <Icon type="" modifier="fw" />
  14. }
  15. function IconRefresh() {
  16. return <Icon type="refresh" modifier="fw" spin />
  17. }
  18. function IconLayout() {
  19. return <Icon type="columns" modifier="fw" />
  20. }
  21. function IconDetach() {
  22. return <Icon type="window-restore" modifier="fw" />
  23. }
  24. function IconCheckmark({ iconFor, pdfLayout, view, detachRole }) {
  25. if (detachRole === 'detacher') {
  26. return <IconPlaceholder />
  27. }
  28. if (iconFor === 'editorOnly' && pdfLayout === 'flat' && view === 'editor') {
  29. return <IconChecked />
  30. } else if (iconFor === 'pdfOnly' && pdfLayout === 'flat' && view === 'pdf') {
  31. return <IconChecked />
  32. } else if (iconFor === 'sideBySide' && pdfLayout === 'sideBySide') {
  33. return <IconChecked />
  34. }
  35. // return empty icon for placeholder
  36. return <IconPlaceholder />
  37. }
  38. function LayoutDropdownButton() {
  39. const { t } = useTranslation()
  40. const {
  41. reattach,
  42. detach,
  43. detachIsLinked,
  44. detachRole,
  45. changeLayout,
  46. view,
  47. pdfLayout,
  48. } = useLayoutContext(layoutContextPropTypes)
  49. const handleDetach = useCallback(() => {
  50. detach()
  51. eventTracking.sendMB('project-layout-detach')
  52. }, [detach])
  53. const handleReattach = useCallback(() => {
  54. if (detachRole !== 'detacher') {
  55. return
  56. }
  57. reattach()
  58. eventTracking.sendMB('project-layout-reattach')
  59. }, [detachRole, reattach])
  60. const handleChangeLayout = useCallback(
  61. (newLayout, newView) => {
  62. handleReattach()
  63. changeLayout(newLayout, newView)
  64. eventTracking.sendMB('project-layout-change', {
  65. layout: newLayout,
  66. view: newView,
  67. })
  68. },
  69. [changeLayout, handleReattach]
  70. )
  71. const processing = !detachIsLinked && detachRole === 'detacher'
  72. // bsStyle is required for Dropdown.Toggle, but we will override style
  73. return (
  74. <>
  75. {processing && (
  76. <div aria-live="assertive" className="sr-only">
  77. {t('layout_processing')}
  78. </div>
  79. )}
  80. <ControlledDropdown
  81. id="layout-dropdown"
  82. className="toolbar-item layout-dropdown"
  83. pullRight
  84. >
  85. <Dropdown.Toggle className="btn-full-height" bsStyle="link">
  86. {processing ? <IconRefresh /> : <IconLayout />}
  87. <span className="toolbar-label">{t('layout')}</span>
  88. <OverlayTrigger
  89. placement="bottom"
  90. overlay={<Tooltip id="pdf-detach-badge">Beta feature</Tooltip>}
  91. delayHide={100}
  92. >
  93. <span className="beta-badge" />
  94. </OverlayTrigger>
  95. </Dropdown.Toggle>
  96. <Dropdown.Menu id="layout-dropdown-list">
  97. <MenuItem onSelect={() => handleChangeLayout('sideBySide')}>
  98. <IconCheckmark
  99. iconFor="sideBySide"
  100. pdfLayout={pdfLayout}
  101. view={view}
  102. detachRole={detachRole}
  103. />
  104. <Icon type="columns" />
  105. {t('editor_and_pdf')}
  106. </MenuItem>
  107. <MenuItem
  108. onSelect={() => handleChangeLayout('flat', 'editor')}
  109. className="menu-item-with-svg"
  110. >
  111. <IconCheckmark
  112. iconFor="editorOnly"
  113. pdfLayout={pdfLayout}
  114. view={view}
  115. detachRole={detachRole}
  116. />
  117. <IconEditorOnly />
  118. <span>
  119. <Trans
  120. i18nKey="editor_only_hide_pdf"
  121. components={[
  122. <span key="editor_only_hide_pdf" className="subdued" />,
  123. ]}
  124. />
  125. </span>
  126. </MenuItem>
  127. <MenuItem
  128. onSelect={() => handleChangeLayout('flat', 'pdf')}
  129. className="menu-item-with-svg"
  130. >
  131. <IconCheckmark
  132. iconFor="pdfOnly"
  133. pdfLayout={pdfLayout}
  134. view={view}
  135. detachRole={detachRole}
  136. />
  137. <IconPdfOnly />
  138. <span>
  139. <Trans
  140. i18nKey="pdf_only_hide_editor"
  141. components={[
  142. <span key="pdf_only_hide_editor" className="subdued" />,
  143. ]}
  144. />
  145. </span>
  146. </MenuItem>
  147. {detachRole === 'detacher' ? (
  148. <MenuItem>
  149. {detachIsLinked ? <IconChecked /> : <IconRefresh />}
  150. <IconDetach />
  151. {t('pdf_in_separate_tab')}
  152. </MenuItem>
  153. ) : (
  154. <MenuItem onSelect={handleDetach}>
  155. <IconPlaceholder />
  156. <IconDetach />
  157. {t('pdf_in_separate_tab')}
  158. </MenuItem>
  159. )}
  160. <MenuItem divider />
  161. <div className="pdf-detach-survey">
  162. <span>
  163. <span className="beta-badge" />
  164. </span>
  165. <span className="pdf-detach-survey-text">
  166. The Layout menu and opening the PDF in a new tab are beta
  167. features.{' '}
  168. <a
  169. href="https://docs.google.com/forms/d/e/1FAIpQLScuxQA8Az9NQwvYgC6FALG7FEtCCj4e8of27e_L0SXGrJFRMw/viewform"
  170. target="_blank"
  171. rel="noopener noreferrer"
  172. >
  173. {t('give_feedback')}.
  174. </a>
  175. </span>
  176. </div>
  177. </Dropdown.Menu>
  178. </ControlledDropdown>
  179. </>
  180. )
  181. }
  182. export default LayoutDropdownButton
  183. IconCheckmark.propTypes = {
  184. iconFor: PropTypes.string.isRequired,
  185. pdfLayout: PropTypes.string.isRequired,
  186. view: PropTypes.string,
  187. detachRole: PropTypes.string,
  188. }
  189. const layoutContextPropTypes = {
  190. reattach: PropTypes.func.isRequired,
  191. detach: PropTypes.func.isRequired,
  192. changeLayout: PropTypes.func.isRequired,
  193. detachIsLinked: PropTypes.bool,
  194. detachRole: PropTypes.string,
  195. pdfLayout: PropTypes.string.isRequired,
  196. view: PropTypes.string,
  197. }