file-tree-data-context.jsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import {
  2. createContext,
  3. useCallback,
  4. useReducer,
  5. useContext,
  6. useMemo,
  7. useState,
  8. } from 'react'
  9. import PropTypes from 'prop-types'
  10. import useScopeValue from '../hooks/use-scope-value'
  11. import {
  12. renameInTree,
  13. deleteInTree,
  14. moveInTree,
  15. createEntityInTree,
  16. } from '../../features/file-tree/util/mutate-in-tree'
  17. import { countFiles } from '../../features/file-tree/util/count-in-tree'
  18. import useDeepCompareEffect from '../../shared/hooks/use-deep-compare-effect'
  19. import { docsInFolder } from '@/features/file-tree/util/docs-in-folder'
  20. const FileTreeDataContext = createContext()
  21. const fileTreeDataPropType = PropTypes.shape({
  22. _id: PropTypes.string.isRequired,
  23. name: PropTypes.string.isRequired,
  24. docs: PropTypes.array.isRequired,
  25. fileRefs: PropTypes.array.isRequired,
  26. folders: PropTypes.array.isRequired,
  27. })
  28. FileTreeDataContext.Provider.propTypes = {
  29. value: PropTypes.shape({
  30. // fileTreeData is the up-to-date representation of the files list, updated
  31. // by the file tree
  32. fileTreeData: fileTreeDataPropType,
  33. hasFolders: PropTypes.bool,
  34. }),
  35. }
  36. const ACTION_TYPES = {
  37. RENAME: 'RENAME',
  38. RESET: 'RESET',
  39. DELETE: 'DELETE',
  40. MOVE: 'MOVE',
  41. CREATE: 'CREATE',
  42. }
  43. function fileTreeMutableReducer({ fileTreeData }, action) {
  44. switch (action.type) {
  45. case ACTION_TYPES.RESET: {
  46. const newFileTreeData = action.fileTreeData
  47. return {
  48. fileTreeData: newFileTreeData,
  49. fileCount: countFiles(newFileTreeData),
  50. }
  51. }
  52. case ACTION_TYPES.RENAME: {
  53. const newFileTreeData = renameInTree(fileTreeData, action.id, {
  54. newName: action.newName,
  55. })
  56. return {
  57. fileTreeData: newFileTreeData,
  58. fileCount: countFiles(newFileTreeData),
  59. }
  60. }
  61. case ACTION_TYPES.DELETE: {
  62. const newFileTreeData = deleteInTree(fileTreeData, action.id)
  63. return {
  64. fileTreeData: newFileTreeData,
  65. fileCount: countFiles(newFileTreeData),
  66. }
  67. }
  68. case ACTION_TYPES.MOVE: {
  69. const newFileTreeData = moveInTree(
  70. fileTreeData,
  71. action.entityId,
  72. action.toFolderId
  73. )
  74. return {
  75. fileTreeData: newFileTreeData,
  76. fileCount: countFiles(newFileTreeData),
  77. }
  78. }
  79. case ACTION_TYPES.CREATE: {
  80. const newFileTreeData = createEntityInTree(
  81. fileTreeData,
  82. action.parentFolderId,
  83. action.entity
  84. )
  85. return {
  86. fileTreeData: newFileTreeData,
  87. fileCount: countFiles(newFileTreeData),
  88. }
  89. }
  90. default: {
  91. throw new Error(`Unknown mutable file tree action type: ${action.type}`)
  92. }
  93. }
  94. }
  95. const initialState = rootFolder => {
  96. const fileTreeData = rootFolder?.[0]
  97. return {
  98. fileTreeData,
  99. fileCount: countFiles(fileTreeData),
  100. }
  101. }
  102. export function useFileTreeData(propTypes) {
  103. const context = useContext(FileTreeDataContext)
  104. if (!context) {
  105. throw new Error(
  106. 'useFileTreeData is only available inside FileTreeDataProvider'
  107. )
  108. }
  109. PropTypes.checkPropTypes(
  110. propTypes,
  111. context,
  112. 'data',
  113. 'FileTreeDataContext.Provider'
  114. )
  115. return context
  116. }
  117. export function FileTreeDataProvider({ children }) {
  118. const [project] = useScopeValue('project')
  119. const { rootFolder } = project || {}
  120. const [{ fileTreeData, fileCount }, dispatch] = useReducer(
  121. fileTreeMutableReducer,
  122. rootFolder,
  123. initialState
  124. )
  125. const [selectedEntities, setSelectedEntities] = useState([])
  126. const docs = useMemo(
  127. () => (fileTreeData ? docsInFolder(fileTreeData) : undefined),
  128. [fileTreeData]
  129. )
  130. useDeepCompareEffect(() => {
  131. dispatch({
  132. type: ACTION_TYPES.RESET,
  133. fileTreeData: rootFolder?.[0],
  134. })
  135. }, [rootFolder])
  136. const dispatchCreateFolder = useCallback((parentFolderId, entity) => {
  137. entity.type = 'folder'
  138. dispatch({
  139. type: ACTION_TYPES.CREATE,
  140. parentFolderId,
  141. entity,
  142. })
  143. }, [])
  144. const dispatchCreateDoc = useCallback((parentFolderId, entity) => {
  145. entity.type = 'doc'
  146. dispatch({
  147. type: ACTION_TYPES.CREATE,
  148. parentFolderId,
  149. entity,
  150. })
  151. }, [])
  152. const dispatchCreateFile = useCallback((parentFolderId, entity) => {
  153. entity.type = 'fileRef'
  154. dispatch({
  155. type: ACTION_TYPES.CREATE,
  156. parentFolderId,
  157. entity,
  158. })
  159. }, [])
  160. const dispatchRename = useCallback((id, newName) => {
  161. dispatch({
  162. type: ACTION_TYPES.RENAME,
  163. newName,
  164. id,
  165. })
  166. }, [])
  167. const dispatchDelete = useCallback(id => {
  168. dispatch({ type: ACTION_TYPES.DELETE, id })
  169. }, [])
  170. const dispatchMove = useCallback((entityId, toFolderId) => {
  171. dispatch({ type: ACTION_TYPES.MOVE, entityId, toFolderId })
  172. }, [])
  173. const value = useMemo(() => {
  174. return {
  175. dispatchCreateDoc,
  176. dispatchCreateFile,
  177. dispatchCreateFolder,
  178. dispatchDelete,
  179. dispatchMove,
  180. dispatchRename,
  181. fileCount,
  182. fileTreeData,
  183. hasFolders: fileTreeData?.folders.length > 0,
  184. selectedEntities,
  185. setSelectedEntities,
  186. docs,
  187. }
  188. }, [
  189. dispatchCreateDoc,
  190. dispatchCreateFile,
  191. dispatchCreateFolder,
  192. dispatchDelete,
  193. dispatchMove,
  194. dispatchRename,
  195. fileCount,
  196. fileTreeData,
  197. selectedEntities,
  198. setSelectedEntities,
  199. docs,
  200. ])
  201. return (
  202. <FileTreeDataContext.Provider value={value}>
  203. {children}
  204. </FileTreeDataContext.Provider>
  205. )
  206. }
  207. FileTreeDataProvider.propTypes = {
  208. children: PropTypes.any,
  209. }