layout-context.tsx 5.5 KB

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