file-tree-mutable.js 4.3 KB

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