Kaynağa Gözat

Migrate layout context to TypeScript (#15275)

Migrate layout context to TypeScript

GitOrigin-RevId: ce453bfb67f7c36176fa24144413b556cd3c117e
Tim Down 2 yıl önce
ebeveyn
işleme
3be937c503

+ 1 - 1
services/web/frontend/js/features/chat/components/chat-pane.jsx

@@ -18,7 +18,7 @@ const MessageList = lazy(() => import('./message-list'))
 const ChatPane = React.memo(function ChatPane() {
   const { t } = useTranslation()
 
-  const { chatIsOpen } = useLayoutContext({ chatIsOpen: PropTypes.bool })
+  const { chatIsOpen } = useLayoutContext()
   const user = useUserContext({
     id: PropTypes.string.isRequired,
   })

+ 1 - 1
services/web/frontend/js/features/chat/context/chat-context.jsx

@@ -136,7 +136,7 @@ export function ChatProvider({ children }) {
     _id: PropTypes.string.isRequired,
   })
 
-  const { chatIsOpen } = useLayoutContext({ chatIsOpen: PropTypes.bool })
+  const { chatIsOpen } = useLayoutContext()
 
   const {
     hasFocus: windowHasFocus,

+ 1 - 3
services/web/frontend/js/features/editor-left-menu/components/settings/settings-overall-theme.tsx

@@ -12,9 +12,7 @@ export default function SettingsOverallTheme() {
   const overallThemes = getMeta('ol-overallThemes') as
     | OverallThemeMeta[]
     | undefined
-  const { loadingStyleSheet } = useLayoutContext() as {
-    loadingStyleSheet: boolean
-  }
+  const { loadingStyleSheet } = useLayoutContext()
   const { overallTheme, setOverallTheme } = useProjectSettingsContext()
 
   const options: Array<Option<OverallTheme>> = useMemo(

+ 1 - 12
services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.jsx

@@ -23,17 +23,6 @@ const editorContextPropTypes = {
   permissionsLevel: PropTypes.string,
 }
 
-const layoutContextPropTypes = {
-  chatIsOpen: PropTypes.bool,
-  setChatIsOpen: PropTypes.func.isRequired,
-  reviewPanelOpen: PropTypes.bool,
-  setReviewPanelOpen: PropTypes.func.isRequired,
-  view: PropTypes.string,
-  setView: PropTypes.func.isRequired,
-  setLeftMenuShown: PropTypes.func.isRequired,
-  pdfLayout: PropTypes.string.isRequired,
-}
-
 const chatContextPropTypes = {
   markMessagesAsRead: PropTypes.func.isRequired,
   unreadMessageCount: PropTypes.number.isRequired,
@@ -70,7 +59,7 @@ const EditorNavigationToolbarRoot = React.memo(
       view,
       setView,
       setLeftMenuShown,
-    } = useLayoutContext(layoutContextPropTypes)
+    } = useLayoutContext()
 
     const { markMessagesAsRead, unreadMessageCount } =
       useChatContext(chatContextPropTypes)

+ 1 - 11
services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.jsx

@@ -107,7 +107,7 @@ function LayoutDropdownButton() {
     changeLayout,
     view,
     pdfLayout,
-  } = useLayoutContext(layoutContextPropTypes)
+  } = useLayoutContext()
 
   const handleDetach = useCallback(() => {
     detach()
@@ -250,13 +250,3 @@ IconCheckmark.propTypes = {
   view: PropTypes.string,
   detachRole: PropTypes.string,
 }
-
-const layoutContextPropTypes = {
-  reattach: PropTypes.func.isRequired,
-  detach: PropTypes.func.isRequired,
-  changeLayout: PropTypes.func.isRequired,
-  detachIsLinked: PropTypes.bool,
-  detachRole: PropTypes.string,
-  pdfLayout: PropTypes.string.isRequired,
-  view: PropTypes.string,
-}

+ 1 - 6
services/web/frontend/js/features/file-tree/contexts/file-tree-selectable.jsx

@@ -211,7 +211,7 @@ const editorContextPropTypes = {
 const isMac = /Mac/.test(window.navigator?.platform)
 
 export function useSelectableEntity(id, isFile) {
-  const { view, setView } = useLayoutContext(layoutContextPropTypes)
+  const { view, setView } = useLayoutContext()
   const { setContextMenuCoords } = useFileTreeMainContext()
   const {
     selectedEntityIds,
@@ -288,11 +288,6 @@ export function useSelectableEntity(id, isFile) {
   return { isSelected, props }
 }
 
-const layoutContextPropTypes = {
-  view: PropTypes.string,
-  setView: PropTypes.func.isRequired,
-}
-
 export function useFileTreeSelectable() {
   const context = useContext(FileTreeSelectableContext)
 

+ 3 - 1
services/web/frontend/js/features/ide-react/context/connection-context.tsx

@@ -22,7 +22,9 @@ type ConnectionContextValue = {
   registerUserActivity: () => void
 }
 
-const ConnectionContext = createContext<ConnectionContextValue | null>(null)
+const ConnectionContext = createContext<ConnectionContextValue | undefined>(
+  undefined
+)
 
 export const ConnectionProvider: FC = ({ children }) => {
   const [connectionManager] = useState(() => new ConnectionManager())

+ 3 - 13
services/web/frontend/js/features/pdf-preview/components/detach-synctex-control.jsx

@@ -1,9 +1,8 @@
-import PropTypes from 'prop-types'
 import { useLayoutContext } from '../../../shared/context/layout-context'
 import PdfSynctexControls from './pdf-synctex-controls'
 
 export function DefaultSynctexControl() {
-  const { detachRole } = useLayoutContext(layoutContextPropTypes)
+  const { detachRole } = useLayoutContext()
   if (!detachRole) {
     return <PdfSynctexControls />
   }
@@ -11,9 +10,7 @@ export function DefaultSynctexControl() {
 }
 
 export function DetacherSynctexControl() {
-  const { detachRole, detachIsLinked } = useLayoutContext(
-    layoutContextPropTypes
-  )
+  const { detachRole, detachIsLinked } = useLayoutContext()
   if (detachRole === 'detacher' && detachIsLinked) {
     return <PdfSynctexControls />
   }
@@ -21,16 +18,9 @@ export function DetacherSynctexControl() {
 }
 
 export function DetachedSynctexControl() {
-  const { detachRole, detachIsLinked } = useLayoutContext(
-    layoutContextPropTypes
-  )
+  const { detachRole, detachIsLinked } = useLayoutContext()
   if (detachRole === 'detached' && detachIsLinked) {
     return <PdfSynctexControls />
   }
   return null
 }
-
-const layoutContextPropTypes = {
-  detachRole: PropTypes.string,
-  detachIsLinked: PropTypes.bool,
-}

+ 59 - 59
services/web/frontend/js/shared/context/layout-context.jsx → services/web/frontend/js/shared/context/layout-context.tsx

@@ -4,104 +4,108 @@ import {
   useCallback,
   useMemo,
   useEffect,
+  Dispatch,
+  SetStateAction,
+  FC,
 } from 'react'
-import PropTypes from 'prop-types'
 import useScopeValue from '../hooks/use-scope-value'
 import useDetachLayout from '../hooks/use-detach-layout'
-import { useIdeContext } from './ide-context'
 import localStorage from '../../infrastructure/local-storage'
 import getMeta from '../../utils/meta'
 import { debugConsole } from '@/utils/debugging'
+import { BinaryFile } from '@/features/file-view/types/binary-file'
+
+export type IdeLayout = 'sideBySide' | 'flat'
+export type IdeView = 'editor' | 'file' | 'pdf' | 'history'
+
+type LayoutContextValue = {
+  reattach: () => void
+  detach: () => void
+  detachIsLinked: boolean
+  detachRole: 'detacher' | 'detached' | null
+  changeLayout: (newLayout: IdeLayout, newView?: IdeView) => void
+  view: IdeView
+  setView: (view: IdeView) => void
+  chatIsOpen: boolean
+  setChatIsOpen: Dispatch<SetStateAction<LayoutContextValue['chatIsOpen']>>
+  reviewPanelOpen: boolean
+  setReviewPanelOpen: Dispatch<
+    SetStateAction<LayoutContextValue['reviewPanelOpen']>
+  >
+  leftMenuShown: boolean
+  setLeftMenuShown: Dispatch<
+    SetStateAction<LayoutContextValue['leftMenuShown']>
+  >
+  loadingStyleSheet: boolean
+  setLoadingStyleSheet: Dispatch<
+    SetStateAction<LayoutContextValue['loadingStyleSheet']>
+  >
+  pdfLayout: IdeLayout
+  pdfPreviewOpen: boolean
+}
 
 const debugPdfDetach = getMeta('ol-debugPdfDetach')
 
-export const LayoutContext = createContext()
-
-LayoutContext.Provider.propTypes = {
-  value: PropTypes.shape({
-    reattach: PropTypes.func.isRequired,
-    detach: PropTypes.func.isRequired,
-    detachIsLinked: PropTypes.bool,
-    detachRole: PropTypes.string,
-    changeLayout: PropTypes.func.isRequired,
-    view: PropTypes.oneOf(['editor', 'file', 'pdf', 'history']),
-    setView: PropTypes.func.isRequired,
-    chatIsOpen: PropTypes.bool,
-    setChatIsOpen: PropTypes.func.isRequired,
-    reviewPanelOpen: PropTypes.bool,
-    setReviewPanelOpen: PropTypes.func.isRequired,
-    leftMenuShown: PropTypes.bool,
-    setLeftMenuShown: PropTypes.func.isRequired,
-    pdfLayout: PropTypes.oneOf(['sideBySide', 'flat']).isRequired,
-    pdfPreviewOpen: PropTypes.bool,
-  }).isRequired,
-}
+export const LayoutContext = createContext<LayoutContextValue | undefined>(
+  undefined
+)
 
-function setLayoutInLocalStorage(pdfLayout) {
+function setLayoutInLocalStorage(pdfLayout: IdeLayout) {
   localStorage.setItem(
     'pdf.layout',
     pdfLayout === 'sideBySide' ? 'split' : 'flat'
   )
 }
 
-export function LayoutProvider({ children }) {
-  const { $scope } = useIdeContext()
-
+export const LayoutProvider: FC = ({ children }) => {
   // what to show in the "flat" view (editor or pdf)
-  const [view, _setView] = useScopeValue('ui.view')
+  const [view, _setView] = useScopeValue<IdeView>('ui.view')
+  const [toggleHistory] = useScopeValue<() => void>('toggleHistory')
+  const [openFile] = useScopeValue<BinaryFile | null>('openFile')
 
   const setView = useCallback(
-    value => {
+    (value: IdeView) => {
       _setView(oldValue => {
         // ensure that the "history:toggle" event is broadcast when switching in or out of history view
         if (value === 'history' || oldValue === 'history') {
-          $scope.toggleHistory()
+          toggleHistory()
         }
 
-        if (value === 'editor' && $scope.openFile) {
+        if (value === 'editor' && openFile) {
           // if a file is currently opened, ensure the view is 'file' instead of
           // 'editor' when the 'editor' view is requested. This is to ensure
           // that the entity selected in the file tree is the one visible and
-          // that docs don't take precendence over files.
+          // that docs don't take precedence over files.
           return 'file'
         }
 
         return value
       })
     },
-    [$scope, _setView]
+    [_setView, openFile, toggleHistory]
   )
 
   // whether the chat pane is open
-  const [chatIsOpen, setChatIsOpen] = useScopeValue('ui.chatOpen')
+  const [chatIsOpen, setChatIsOpen] = useScopeValue<boolean>('ui.chatOpen')
 
   // whether the review pane is open
   const [reviewPanelOpen, setReviewPanelOpen] =
     useScopeValue('ui.reviewPanelOpen')
 
   // whether the menu pane is open
-  const [leftMenuShown, setLeftMenuShown] = useScopeValue('ui.leftMenuShown')
+  const [leftMenuShown, setLeftMenuShown] =
+    useScopeValue<boolean>('ui.leftMenuShown')
 
   // whether to display the editor and preview side-by-side or full-width ("flat")
-  const [pdfLayout, setPdfLayout] = useScopeValue('ui.pdfLayout')
+  const [pdfLayout, setPdfLayout] = useScopeValue<IdeLayout>('ui.pdfLayout')
 
   // whether stylesheet on theme is loading
-  const [loadingStyleSheet, setLoadingStyleSheet] = useScopeValue(
+  const [loadingStyleSheet, setLoadingStyleSheet] = useScopeValue<boolean>(
     'ui.loadingStyleSheet'
   )
 
-  // switch to either side-by-side or flat (full-width) layout
-  const switchLayout = useCallback(() => {
-    setPdfLayout(layout => {
-      const newLayout = layout === 'sideBySide' ? 'flat' : 'sideBySide'
-      setView(newLayout === 'sideBySide' ? 'editor' : 'pdf')
-      setPdfLayout(newLayout)
-      setLayoutInLocalStorage(newLayout)
-    })
-  }, [setPdfLayout, setView])
-
   const changeLayout = useCallback(
-    (newLayout, newView) => {
+    (newLayout: IdeLayout, newView: IdeView = 'editor') => {
       setPdfLayout(newLayout)
       setView(newLayout === 'sideBySide' ? 'editor' : newView)
       setLayoutInLocalStorage(newLayout)
@@ -151,7 +155,7 @@ export function LayoutProvider({ children }) {
     changeLayout,
   ])
 
-  const value = useMemo(
+  const value = useMemo<LayoutContextValue>(
     () => ({
       reattach,
       detach,
@@ -170,7 +174,6 @@ export function LayoutProvider({ children }) {
       setReviewPanelOpen,
       setLoadingStyleSheet,
       setView,
-      switchLayout,
       view,
     }),
     [
@@ -191,7 +194,6 @@ export function LayoutProvider({ children }) {
       setReviewPanelOpen,
       setLoadingStyleSheet,
       setView,
-      switchLayout,
       view,
     ]
   )
@@ -201,12 +203,10 @@ export function LayoutProvider({ children }) {
   )
 }
 
-LayoutProvider.propTypes = {
-  children: PropTypes.any,
-}
-
-export function useLayoutContext(propTypes) {
-  const data = useContext(LayoutContext)
-  PropTypes.checkPropTypes(propTypes, data, 'data', 'LayoutContext.Provider')
-  return data
+export function useLayoutContext() {
+  const context = useContext(LayoutContext)
+  if (!context) {
+    throw new Error('useLayoutContext is only available inside LayoutProvider')
+  }
+  return context
 }

+ 9 - 2
services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx

@@ -2,7 +2,11 @@ import localStorage from '../../../../frontend/js/infrastructure/local-storage'
 import PdfPreview from '../../../../frontend/js/features/pdf-preview/components/pdf-preview'
 import { EditorProviders } from '../../helpers/editor-providers'
 import { mockScope } from './scope'
-import { useLayoutContext } from '../../../../frontend/js/shared/context/layout-context'
+import {
+  IdeLayout,
+  IdeView,
+  useLayoutContext,
+} from '../../../../frontend/js/shared/context/layout-context'
 import { FC, useEffect } from 'react'
 
 const storeAndFireEvent = (win: typeof window, key: string, value: unknown) => {
@@ -10,7 +14,10 @@ const storeAndFireEvent = (win: typeof window, key: string, value: unknown) => {
   win.dispatchEvent(new StorageEvent('storage', { key }))
 }
 
-const Layout: FC<{ layout: string; view?: string }> = ({ layout, view }) => {
+const Layout: FC<{ layout: IdeLayout; view?: IdeView }> = ({
+  layout,
+  view,
+}) => {
   const { changeLayout } = useLayoutContext()
 
   useEffect(() => {