file-tree-mutable.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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. return (
  75. <FileTreeMutableContext.Provider
  76. value={{ fileTreeData, fileCount, dispatch }}
  77. >
  78. {children}
  79. </FileTreeMutableContext.Provider>
  80. )
  81. }
  82. FileTreeMutableProvider.propTypes = {
  83. rootFolder: PropTypes.array.isRequired,
  84. children: PropTypes.oneOfType([
  85. PropTypes.arrayOf(PropTypes.node),
  86. PropTypes.node
  87. ]).isRequired
  88. }
  89. export function useFileTreeMutable() {
  90. const { fileTreeData, fileCount, dispatch } = useContext(
  91. FileTreeMutableContext
  92. )
  93. const dispatchCreateFolder = useCallback(
  94. (parentFolderId, entity) => {
  95. entity.type = 'folder'
  96. dispatch({
  97. type: ACTION_TYPES.CREATE_ENTITY,
  98. parentFolderId,
  99. entity
  100. })
  101. },
  102. [dispatch]
  103. )
  104. const dispatchCreateDoc = useCallback(
  105. (parentFolderId, entity) => {
  106. entity.type = 'doc'
  107. dispatch({
  108. type: ACTION_TYPES.CREATE_ENTITY,
  109. parentFolderId,
  110. entity
  111. })
  112. },
  113. [dispatch]
  114. )
  115. const dispatchCreateFile = useCallback(
  116. (parentFolderId, entity) => {
  117. entity.type = 'fileRef'
  118. dispatch({
  119. type: ACTION_TYPES.CREATE_ENTITY,
  120. parentFolderId,
  121. entity
  122. })
  123. },
  124. [dispatch]
  125. )
  126. const dispatchRename = useCallback(
  127. (id, newName) => {
  128. dispatch({
  129. type: ACTION_TYPES.RENAME,
  130. newName,
  131. id
  132. })
  133. },
  134. [dispatch]
  135. )
  136. const dispatchDelete = useCallback(
  137. id => {
  138. dispatch({ type: ACTION_TYPES.DELETE, id })
  139. },
  140. [dispatch]
  141. )
  142. const dispatchMove = useCallback(
  143. (entityId, toFolderId) => {
  144. dispatch({ type: ACTION_TYPES.MOVE, entityId, toFolderId })
  145. },
  146. [dispatch]
  147. )
  148. return {
  149. fileTreeData,
  150. fileCount,
  151. dispatchRename,
  152. dispatchDelete,
  153. dispatchMove,
  154. dispatchCreateFolder,
  155. dispatchCreateDoc,
  156. dispatchCreateFile
  157. }
  158. }
  159. function filesInFolder({ docs, folders, fileRefs }) {
  160. const files = [...docs, ...fileRefs]
  161. for (const folder of folders) {
  162. files.push(...filesInFolder(folder))
  163. }
  164. return files
  165. }
  166. function countFiles(fileTreeData) {
  167. const files = filesInFolder(fileTreeData)
  168. // count all the non-deleted entities
  169. const value = files.filter(item => !item.deleted).length
  170. const limit = window.ExposedSettings.maxEntitiesPerProject
  171. const status = fileCountStatus(value, limit, Math.ceil(limit / 20))
  172. return { value, status, limit }
  173. }
  174. function fileCountStatus(value, limit, range) {
  175. if (value >= limit) {
  176. return 'error'
  177. }
  178. if (value >= limit - range) {
  179. return 'warning'
  180. }
  181. return 'success'
  182. }