layout-context.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  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.oneOf(['editor', 'file', 'pdf', 'history']),
  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. pdfPreviewOpen: PropTypes.bool,
  33. }).isRequired,
  34. }
  35. function setLayoutInLocalStorage(pdfLayout) {
  36. localStorage.setItem(
  37. 'pdf.layout',
  38. pdfLayout === 'sideBySide' ? 'split' : 'flat'
  39. )
  40. }
  41. export function LayoutProvider({ children }) {
  42. const { $scope } = useIdeContext()
  43. // what to show in the "flat" view (editor or pdf)
  44. const [view, _setView] = useScopeValue('ui.view')
  45. const setView = useCallback(
  46. value => {
  47. _setView(oldValue => {
  48. // ensure that the "history:toggle" event is broadcast when switching in or out of history view
  49. if (value === 'history' || oldValue === 'history') {
  50. $scope.toggleHistory()
  51. }
  52. if (value === 'editor' && $scope.openFile) {
  53. // if a file is currently opened, ensure the view is 'file' instead of
  54. // 'editor' when the 'editor' view is requested. This is to ensure
  55. // that the entity selected in the file tree is the one visible and
  56. // that docs don't take precendence over files.
  57. return 'file'
  58. }
  59. return value
  60. })
  61. },
  62. [$scope, _setView]
  63. )
  64. // whether the chat pane is open
  65. const [chatIsOpen, setChatIsOpen] = useScopeValue('ui.chatOpen')
  66. // whether the review pane is open
  67. const [reviewPanelOpen, setReviewPanelOpen] =
  68. useScopeValue('ui.reviewPanelOpen')
  69. // whether the menu pane is open
  70. const [leftMenuShown, setLeftMenuShown] = useScopeValue('ui.leftMenuShown')
  71. // whether to display the editor and preview side-by-side or full-width ("flat")
  72. const [pdfLayout, setPdfLayout] = useScopeValue('ui.pdfLayout')
  73. // whether stylesheet on theme is loading
  74. const [loadingStyleSheet, setLoadingStyleSheet] = useScopeValue(
  75. 'ui.loadingStyleSheet'
  76. )
  77. // switch to either side-by-side or flat (full-width) layout
  78. const switchLayout = useCallback(() => {
  79. setPdfLayout(layout => {
  80. const newLayout = layout === 'sideBySide' ? 'flat' : 'sideBySide'
  81. setView(newLayout === 'sideBySide' ? 'editor' : 'pdf')
  82. setPdfLayout(newLayout)
  83. setLayoutInLocalStorage(newLayout)
  84. })
  85. }, [setPdfLayout, setView])
  86. const changeLayout = useCallback(
  87. (newLayout, newView) => {
  88. setPdfLayout(newLayout)
  89. setView(newLayout === 'sideBySide' ? 'editor' : newView)
  90. setLayoutInLocalStorage(newLayout)
  91. },
  92. [setPdfLayout, setView]
  93. )
  94. const {
  95. reattach,
  96. detach,
  97. isLinking: detachIsLinking,
  98. isLinked: detachIsLinked,
  99. role: detachRole,
  100. isRedundant: detachIsRedundant,
  101. } = useDetachLayout()
  102. const pdfPreviewOpen =
  103. pdfLayout === 'sideBySide' || view === 'pdf' || detachRole === 'detacher'
  104. useEffect(() => {
  105. if (debugPdfDetach) {
  106. console.log('Layout Effect', {
  107. detachIsRedundant,
  108. detachRole,
  109. detachIsLinking,
  110. detachIsLinked,
  111. })
  112. }
  113. if (detachRole !== 'detacher') return // not in a PDF detacher layout
  114. if (detachIsRedundant) {
  115. changeLayout('sideBySide')
  116. return
  117. }
  118. if (detachIsLinking || detachIsLinked) {
  119. // the tab is linked to a detached tab (or about to be linked); show
  120. // editor only
  121. changeLayout('flat', 'editor')
  122. }
  123. }, [
  124. detachIsRedundant,
  125. detachRole,
  126. detachIsLinking,
  127. detachIsLinked,
  128. changeLayout,
  129. ])
  130. const value = useMemo(
  131. () => ({
  132. reattach,
  133. detach,
  134. detachIsLinked,
  135. detachRole,
  136. changeLayout,
  137. chatIsOpen,
  138. leftMenuShown,
  139. pdfLayout,
  140. pdfPreviewOpen,
  141. reviewPanelOpen,
  142. loadingStyleSheet,
  143. setChatIsOpen,
  144. setLeftMenuShown,
  145. setPdfLayout,
  146. setReviewPanelOpen,
  147. setLoadingStyleSheet,
  148. setView,
  149. switchLayout,
  150. view,
  151. }),
  152. [
  153. reattach,
  154. detach,
  155. detachIsLinked,
  156. detachRole,
  157. changeLayout,
  158. chatIsOpen,
  159. leftMenuShown,
  160. pdfLayout,
  161. pdfPreviewOpen,
  162. reviewPanelOpen,
  163. loadingStyleSheet,
  164. setChatIsOpen,
  165. setLeftMenuShown,
  166. setPdfLayout,
  167. setReviewPanelOpen,
  168. setLoadingStyleSheet,
  169. setView,
  170. switchLayout,
  171. view,
  172. ]
  173. )
  174. return (
  175. <LayoutContext.Provider value={value}>{children}</LayoutContext.Provider>
  176. )
  177. }
  178. LayoutProvider.propTypes = {
  179. children: PropTypes.any,
  180. }
  181. export function useLayoutContext(propTypes) {
  182. const data = useContext(LayoutContext)
  183. PropTypes.checkPropTypes(propTypes, data, 'data', 'LayoutContext.Provider')
  184. return data
  185. }