layout-dropdown-button.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import { useCallback } from 'react'
  2. import PropTypes from 'prop-types'
  3. import { Dropdown, MenuItem } 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"
  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. </Dropdown.Toggle>
  89. <Dropdown.Menu id="layout-dropdown-list">
  90. <MenuItem onSelect={() => handleChangeLayout('sideBySide')}>
  91. <IconCheckmark
  92. iconFor="sideBySide"
  93. pdfLayout={pdfLayout}
  94. view={view}
  95. detachRole={detachRole}
  96. />
  97. <Icon type="columns" />
  98. {t('editor_and_pdf')}
  99. </MenuItem>
  100. <MenuItem
  101. onSelect={() => handleChangeLayout('flat', 'editor')}
  102. className="menu-item-with-svg"
  103. >
  104. <IconCheckmark
  105. iconFor="editorOnly"
  106. pdfLayout={pdfLayout}
  107. view={view}
  108. detachRole={detachRole}
  109. />
  110. <IconEditorOnly />
  111. <Trans
  112. i18nKey="editor_only_hide_pdf"
  113. components={[
  114. <span key="editor_only_hide_pdf" className="subdued" />,
  115. ]}
  116. />
  117. </MenuItem>
  118. <MenuItem
  119. onSelect={() => handleChangeLayout('flat', 'pdf')}
  120. className="menu-item-with-svg"
  121. >
  122. <IconCheckmark
  123. iconFor="pdfOnly"
  124. pdfLayout={pdfLayout}
  125. view={view}
  126. detachRole={detachRole}
  127. />
  128. <IconPdfOnly />
  129. <Trans
  130. i18nKey="pdf_only_hide_editor"
  131. components={[
  132. <span key="pdf_only_hide_editor" className="subdued" />,
  133. ]}
  134. />
  135. </MenuItem>
  136. {detachRole === 'detacher' ? (
  137. <MenuItem>
  138. {detachIsLinked ? <IconChecked /> : <IconRefresh />}
  139. <IconDetach />
  140. {t('pdf_in_separate_tab')}
  141. </MenuItem>
  142. ) : (
  143. <MenuItem onSelect={handleDetach}>
  144. <IconPlaceholder />
  145. <IconDetach />
  146. {t('pdf_in_separate_tab')}
  147. </MenuItem>
  148. )}
  149. </Dropdown.Menu>
  150. </ControlledDropdown>
  151. </>
  152. )
  153. }
  154. export default LayoutDropdownButton
  155. IconCheckmark.propTypes = {
  156. iconFor: PropTypes.string.isRequired,
  157. pdfLayout: PropTypes.string.isRequired,
  158. view: PropTypes.string,
  159. detachRole: PropTypes.string,
  160. }
  161. const layoutContextPropTypes = {
  162. reattach: PropTypes.func.isRequired,
  163. detach: PropTypes.func.isRequired,
  164. changeLayout: PropTypes.func.isRequired,
  165. detachIsLinked: PropTypes.bool,
  166. detachRole: PropTypes.string,
  167. pdfLayout: PropTypes.string.isRequired,
  168. view: PropTypes.string,
  169. }