file-tree-mutable.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import {
  2. createContext,
  3. useCallback,
  4. useReducer,
  5. useContext,
  6. useEffect,
  7. } from 'react'
  8. import PropTypes from 'prop-types'
  9. import {
  10. renameInTree,
  11. deleteInTree,
  12. moveInTree,
  13. createEntityInTree,
  14. } from '../util/mutate-in-tree'
  15. import { useProjectContext } from '../../../shared/context/project-context'
  16. const FileTreeMutableContext = createContext()
  17. const ACTION_TYPES = {
  18. RENAME: 'RENAME',
  19. RESET: 'RESET',
  20. DELETE: 'DELETE',
  21. MOVE: 'MOVE',
  22. CREATE: 'CREATE',
  23. }
  24. function fileTreeMutableReducer({ fileTreeData }, action) {
  25. switch (action.type) {
  26. case ACTION_TYPES.RESET: {
  27. const newFileTreeData = action.fileTreeData
  28. return {
  29. fileTreeData: newFileTreeData,
  30. fileCount: countFiles(newFileTreeData),
  31. }
  32. }
  33. case ACTION_TYPES.RENAME: {
  34. const newFileTreeData = renameInTree(fileTreeData, action.id, {
  35. newName: action.newName,
  36. })
  37. return {
  38. fileTreeData: newFileTreeData,
  39. fileCount: countFiles(newFileTreeData),
  40. }
  41. }
  42. case ACTION_TYPES.DELETE: {
  43. const newFileTreeData = deleteInTree(fileTreeData, action.id)
  44. return {
  45. fileTreeData: newFileTreeData,
  46. fileCount: countFiles(newFileTreeData),
  47. }
  48. }
  49. case ACTION_TYPES.MOVE: {
  50. const newFileTreeData = moveInTree(
  51. fileTreeData,
  52. action.entityId,
  53. action.toFolderId
  54. )
  55. return {
  56. fileTreeData: newFileTreeData,
  57. fileCount: countFiles(newFileTreeData),
  58. }
  59. }
  60. case ACTION_TYPES.CREATE: {
  61. const newFileTreeData = createEntityInTree(
  62. fileTreeData,
  63. action.parentFolderId,
  64. action.entity
  65. )
  66. return {
  67. fileTreeData: newFileTreeData,
  68. fileCount: countFiles(newFileTreeData),
  69. }
  70. }
  71. default: {
  72. throw new Error(`Unknown mutable file tree action type: ${action.type}`)
  73. }
  74. }
  75. }
  76. const initialState = rootFolder => ({
  77. fileTreeData: rootFolder[0],
  78. fileCount: countFiles(rootFolder[0]),
  79. })
  80. export const FileTreeMutableProvider = function ({ children }) {
  81. const { rootFolder } = useProjectContext(projectContextPropTypes)
  82. const [{ fileTreeData, fileCount }, dispatch] = useReducer(
  83. fileTreeMutableReducer,
  84. rootFolder,
  85. initialState
  86. )
  87. useEffect(() => {
  88. dispatch({
  89. type: ACTION_TYPES.RESET,
  90. fileTreeData: rootFolder[0],
  91. })
  92. }, [rootFolder])
  93. const dispatchCreateFolder = useCallback((parentFolderId, entity) => {
  94. entity.type = 'folder'
  95. dispatch({
  96. type: ACTION_TYPES.CREATE,
  97. parentFolderId,
  98. entity,
  99. })
  100. }, [])
  101. const dispatchCreateDoc = useCallback((parentFolderId, entity) => {
  102. entity.type = 'doc'
  103. dispatch({
  104. type: ACTION_TYPES.CREATE,
  105. parentFolderId,
  106. entity,
  107. })
  108. }, [])
  109. const dispatchCreateFile = useCallback((parentFolderId, entity) => {
  110. entity.type = 'fileRef'
  111. dispatch({
  112. type: ACTION_TYPES.CREATE,
  113. parentFolderId,
  114. entity,
  115. })
  116. }, [])
  117. const dispatchRename = useCallback((id, newName) => {
  118. dispatch({
  119. type: ACTION_TYPES.RENAME,
  120. newName,
  121. id,
  122. })
  123. }, [])
  124. const dispatchDelete = useCallback(id => {
  125. dispatch({ type: ACTION_TYPES.DELETE, id })
  126. }, [])
  127. const dispatchMove = useCallback((entityId, toFolderId) => {
  128. dispatch({ type: ACTION_TYPES.MOVE, entityId, toFolderId })
  129. }, [])
  130. const value = {
  131. dispatchCreateDoc,
  132. dispatchCreateFile,
  133. dispatchCreateFolder,
  134. dispatchDelete,
  135. dispatchMove,
  136. dispatchRename,
  137. fileCount,
  138. fileTreeData,
  139. }
  140. return (
  141. <FileTreeMutableContext.Provider value={value}>
  142. {children}
  143. </FileTreeMutableContext.Provider>
  144. )
  145. }
  146. FileTreeMutableProvider.propTypes = {
  147. children: PropTypes.oneOfType([
  148. PropTypes.arrayOf(PropTypes.node),
  149. PropTypes.node,
  150. ]).isRequired,
  151. }
  152. const projectContextPropTypes = {
  153. rootFolder: PropTypes.arrayOf(
  154. PropTypes.shape({
  155. _id: PropTypes.string.isRequired,
  156. name: PropTypes.string.isRequired,
  157. docs: PropTypes.array.isRequired,
  158. fileRefs: PropTypes.array.isRequired,
  159. folders: PropTypes.array.isRequired,
  160. })
  161. ),
  162. }
  163. export function useFileTreeMutable() {
  164. const context = useContext(FileTreeMutableContext)
  165. if (!context) {
  166. throw new Error(
  167. 'useFileTreeMutable is only available in FileTreeMutableProvider'
  168. )
  169. }
  170. return context
  171. }
  172. function filesInFolder({ docs, folders, fileRefs }) {
  173. const files = [...docs, ...fileRefs]
  174. for (const folder of folders) {
  175. files.push(...filesInFolder(folder))
  176. }
  177. return files
  178. }
  179. function countFiles(fileTreeData) {
  180. const files = filesInFolder(fileTreeData)
  181. // count all the non-deleted entities
  182. const value = files.filter(item => !item.deleted).length
  183. const limit = window.ExposedSettings.maxEntitiesPerProject
  184. const status = fileCountStatus(value, limit, Math.ceil(limit / 20))
  185. return { value, status, limit }
  186. }
  187. function fileCountStatus(value, limit, range) {
  188. if (value >= limit) {
  189. return 'error'
  190. }
  191. if (value >= limit - range) {
  192. return 'warning'
  193. }
  194. return 'success'
  195. }