layout-dropdown-button.js 6.5 KB

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