file-tree-mutable.js 4.7 KB

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