layout-context.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import {
  2. createContext,
  3. useContext,
  4. useCallback,
  5. useMemo,
  6. useEffect,
  7. } from 'react'
  8. import PropTypes from 'prop-types'
  9. import useScopeValue from '../hooks/use-scope-value'
  10. import useDetachLayout from '../hooks/use-detach-layout'
  11. import { useIdeContext } from './ide-context'
  12. import localStorage from '../../infrastructure/local-storage'
  13. import getMeta from '../../utils/meta'
  14. const debugPdfDetach = getMeta('ol-debugPdfDetach')
  15. export const LayoutContext = createContext()
  16. LayoutContext.Provider.propTypes = {
  17. value: PropTypes.shape({
  18. reattach: PropTypes.func.isRequired,
  19. detach: PropTypes.func.isRequired,
  20. detachIsLinked: PropTypes.bool,
  21. detachRole: PropTypes.string,
  22. changeLayout: PropTypes.func.isRequired,
  23. view: PropTypes.string,
  24. setView: PropTypes.func.isRequired,
  25. chatIsOpen: PropTypes.bool,
  26. setChatIsOpen: PropTypes.func.isRequired,
  27. reviewPanelOpen: PropTypes.bool,
  28. setReviewPanelOpen: PropTypes.func.isRequired,
  29. leftMenuShown: PropTypes.bool,
  30. setLeftMenuShown: PropTypes.func.isRequired,
  31. pdfLayout: PropTypes.oneOf(['sideBySide', 'flat']).isRequired,
  32. }).isRequired,
  33. }
  34. function setLayoutInLocalStorage(pdfLayout) {
  35. localStorage.setItem(
  36. 'pdf.layout',
  37. pdfLayout === 'sideBySide' ? 'split' : 'flat'
  38. )
  39. }
  40. export function LayoutProvider({ children }) {
  41. const { $scope } = useIdeContext()
  42. // what to show in the "flat" view (editor or pdf)
  43. const [view, _setView] = useScopeValue('ui.view')
  44. const setView = useCallback(
  45. value => {
  46. _setView(oldValue => {
  47. // ensure that the "history:toggle" event is broadcast when switching in or out of history view
  48. if (value === 'history' || oldValue === 'history') {
  49. $scope.toggleHistory()
  50. }
  51. return value
  52. })
  53. },
  54. [$scope, _setView]
  55. )
  56. // whether the chat pane is open
  57. const [chatIsOpen, setChatIsOpen] = useScopeValue('ui.chatOpen')
  58. // whether the review pane is open
  59. const [reviewPanelOpen, setReviewPanelOpen] = useScopeValue(
  60. 'ui.reviewPanelOpen'
  61. )
  62. // whether the menu pane is open
  63. const [leftMenuShown, setLeftMenuShown] = useScopeValue('ui.leftMenuShown')
  64. // whether to display the editor and preview side-by-side or full-width ("flat")
  65. const [pdfLayout, setPdfLayout] = useScopeValue('ui.pdfLayout')
  66. // switch to either side-by-side or flat (full-width) layout
  67. const switchLayout = useCallback(() => {
  68. setPdfLayout(layout => {
  69. const newLayout = layout === 'sideBySide' ? 'flat' : 'sideBySide'
  70. setView(newLayout === 'sideBySide' ? 'editor' : 'pdf')
  71. setPdfLayout(newLayout)
  72. setLayoutInLocalStorage(newLayout)
  73. })
  74. }, [setPdfLayout, setView])
  75. const changeLayout = useCallback(
  76. (newLayout, newView) => {
  77. setPdfLayout(newLayout)
  78. setView(newLayout === 'sideBySide' ? 'editor' : newView)
  79. setLayoutInLocalStorage(newLayout)
  80. },
  81. [setPdfLayout, setView]
  82. )
  83. const {
  84. reattach,
  85. detach,
  86. isLinking: detachIsLinking,
  87. isLinked: detachIsLinked,
  88. role: detachRole,
  89. } = useDetachLayout()
  90. useEffect(() => {
  91. if (debugPdfDetach) {
  92. console.log('Layout Effect', {
  93. detachRole,
  94. detachIsLinking,
  95. detachIsLinked,
  96. })
  97. }
  98. if (detachRole !== 'detacher') return // not in a PDF detacher layout
  99. if (detachIsLinking || detachIsLinked) {
  100. // the tab is linked to a detached tab (or about to be linked); show
  101. // editor only
  102. changeLayout('flat', 'editor')
  103. }
  104. }, [detachRole, detachIsLinking, detachIsLinked, changeLayout])
  105. const value = useMemo(
  106. () => ({
  107. reattach,
  108. detach,
  109. detachIsLinked,
  110. detachRole,
  111. changeLayout,
  112. chatIsOpen,
  113. leftMenuShown,
  114. pdfLayout,
  115. reviewPanelOpen,
  116. setChatIsOpen,
  117. setLeftMenuShown,
  118. setPdfLayout,
  119. setReviewPanelOpen,
  120. setView,
  121. switchLayout,
  122. view,
  123. }),
  124. [
  125. reattach,
  126. detach,
  127. detachIsLinked,
  128. detachRole,
  129. changeLayout,
  130. chatIsOpen,
  131. leftMenuShown,
  132. pdfLayout,
  133. reviewPanelOpen,
  134. setChatIsOpen,
  135. setLeftMenuShown,
  136. setPdfLayout,
  137. setReviewPanelOpen,
  138. setView,
  139. switchLayout,
  140. view,
  141. ]
  142. )
  143. return (
  144. <LayoutContext.Provider value={value}>{children}</LayoutContext.Provider>
  145. )
  146. }
  147. LayoutProvider.propTypes = {
  148. children: PropTypes.any,
  149. }
  150. export function useLayoutContext(propTypes) {
  151. const data = useContext(LayoutContext)
  152. PropTypes.checkPropTypes(propTypes, data, 'data', 'LayoutContext.Provider')
  153. return data
  154. }