file-tree-open-context.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import {
  2. createContext,
  3. FC,
  4. useCallback,
  5. useContext,
  6. useEffect,
  7. useMemo,
  8. useRef,
  9. useState,
  10. } from 'react'
  11. import { useProjectContext } from '@/shared/context/project-context'
  12. import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
  13. import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
  14. import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
  15. import {
  16. FileTreeDocumentFindResult,
  17. FileTreeFileRefFindResult,
  18. FileTreeFindResult,
  19. } from '@/features/ide-react/types/file-tree'
  20. import { debugConsole } from '@/utils/debugging'
  21. import { convertFileRefToBinaryFile } from '@/features/ide-react/util/file-view'
  22. import { sendMB } from '@/infrastructure/event-tracking'
  23. import { FileRef } from '../../../../../types/file-ref'
  24. import { useLayoutContext } from '@/shared/context/layout-context'
  25. const FileTreeOpenContext = createContext<
  26. | {
  27. selectedEntityCount: number
  28. openEntity: FileTreeDocumentFindResult | FileTreeFileRefFindResult | null
  29. handleFileTreeInit: () => void
  30. handleFileTreeSelect: (selectedEntities: FileTreeFindResult[]) => void
  31. handleFileTreeDelete: (entity: FileTreeFindResult) => void
  32. fileTreeExpanded: boolean
  33. toggleFileTreeExpanded: () => void
  34. expandFileTree: () => void
  35. collapseFileTree: () => void
  36. }
  37. | undefined
  38. >(undefined)
  39. export const FileTreeOpenProvider: FC<React.PropsWithChildren> = ({
  40. children,
  41. }) => {
  42. const { project } = useProjectContext()
  43. const rootDocId = project?.rootDocId
  44. const projectOwner = project?.owner?._id
  45. const { eventEmitter, projectJoined } = useIdeReactContext()
  46. const { openDocWithId, openInitialDoc } = useEditorManagerContext()
  47. const { currentDocumentId } = useEditorOpenDocContext()
  48. const { setOpenFile } = useLayoutContext()
  49. const [openEntity, setOpenEntity] = useState<
  50. FileTreeDocumentFindResult | FileTreeFileRefFindResult | null
  51. >(null)
  52. const [selectedEntityCount, setSelectedEntityCount] = useState(0)
  53. const [fileTreeReady, setFileTreeReady] = useState(false)
  54. // NOTE: Only used in editor redesign
  55. const [fileTreeExpanded, setFileTreeExpanded] = useState(true)
  56. const toggleFileTreeExpanded = useCallback(() => {
  57. setFileTreeExpanded(prev => !prev)
  58. }, [])
  59. const expandFileTree = useCallback(() => {
  60. setFileTreeExpanded(true)
  61. }, [])
  62. const collapseFileTree = useCallback(() => {
  63. setFileTreeExpanded(false)
  64. }, [])
  65. const handleFileTreeInit = useCallback(() => {
  66. setFileTreeReady(true)
  67. }, [])
  68. // Open a document in the editor when one is selected in the file tree
  69. const handleFileTreeSelect = useCallback(
  70. (selectedEntities: FileTreeFindResult[]) => {
  71. debugConsole.log('File tree selection changed', selectedEntities)
  72. setSelectedEntityCount(selectedEntities.length)
  73. if (selectedEntities.length !== 1) {
  74. setOpenEntity(null)
  75. return
  76. }
  77. const [selected] = selectedEntities
  78. if (selected.type === 'folder') {
  79. return
  80. }
  81. setOpenEntity(selected)
  82. if (selected.type === 'doc' && fileTreeReady) {
  83. openDocWithId(selected.entity._id, { keepCurrentView: true })
  84. if (selected.entity.name.endsWith('.bib')) {
  85. sendMB('open-bib-file', {
  86. projectOwner,
  87. isSampleFile: selected.entity.name === 'sample.bib',
  88. linkedFileProvider: null,
  89. })
  90. }
  91. }
  92. // Keep openFile scope value in sync with the file tree
  93. const openFile =
  94. selected.type === 'fileRef'
  95. ? convertFileRefToBinaryFile(selected.entity)
  96. : null
  97. setOpenFile(openFile)
  98. if (openFile) {
  99. if (selected?.entity?.name?.endsWith('.bib')) {
  100. sendMB('open-bib-file', {
  101. projectOwner,
  102. isSampleFile: false,
  103. linkedFileProvider: (selected.entity as FileRef).linkedFileData
  104. ?.provider,
  105. })
  106. }
  107. window.dispatchEvent(new CustomEvent('file-view:file-opened'))
  108. }
  109. },
  110. [fileTreeReady, setOpenFile, openDocWithId, projectOwner]
  111. )
  112. const handleFileTreeDelete = useCallback(
  113. (entity: FileTreeFindResult, isFileRestore?: boolean) => {
  114. eventEmitter.emit('entity:deleted', entity)
  115. // Select the root document if the current document was deleted and delete is not part of a file restore
  116. if (!isFileRestore && entity.entity._id === currentDocumentId) {
  117. openDocWithId(rootDocId!)
  118. }
  119. },
  120. [eventEmitter, currentDocumentId, openDocWithId, rootDocId]
  121. )
  122. // Open a document once the file tree and project are ready
  123. const initialOpenDoneRef = useRef(false)
  124. useEffect(() => {
  125. if (fileTreeReady && projectJoined && !initialOpenDoneRef.current) {
  126. initialOpenDoneRef.current = true
  127. openInitialDoc(rootDocId)
  128. }
  129. }, [fileTreeReady, openInitialDoc, projectJoined, rootDocId])
  130. const value = useMemo(() => {
  131. return {
  132. selectedEntityCount,
  133. openEntity,
  134. handleFileTreeInit,
  135. handleFileTreeSelect,
  136. handleFileTreeDelete,
  137. fileTreeExpanded,
  138. toggleFileTreeExpanded,
  139. expandFileTree,
  140. collapseFileTree,
  141. }
  142. }, [
  143. handleFileTreeDelete,
  144. handleFileTreeInit,
  145. handleFileTreeSelect,
  146. openEntity,
  147. selectedEntityCount,
  148. fileTreeExpanded,
  149. toggleFileTreeExpanded,
  150. expandFileTree,
  151. collapseFileTree,
  152. ])
  153. return (
  154. <FileTreeOpenContext.Provider value={value}>
  155. {children}
  156. </FileTreeOpenContext.Provider>
  157. )
  158. }
  159. export const useFileTreeOpenContext = () => {
  160. const context = useContext(FileTreeOpenContext)
  161. if (!context) {
  162. throw new Error(
  163. 'useFileTreeOpenContext is only available inside FileTreeOpenProvider'
  164. )
  165. }
  166. return context
  167. }