file-tree-data-context.js 5.1 KB

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