layout-context.js 5.6 KB

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