file-tree.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import _ from 'lodash'
  2. import type { FileDiff, FileRenamed } from '../services/types/file'
  3. import type { DiffOperation } from '../services/types/diff-operation'
  4. // `Partial` because the `reducePathsToTree` function was copied directly
  5. // from a javascript file without proper type system and the logic is not typescript-friendly.
  6. // TODO: refactor the function to have a proper type system
  7. type FileTreeEntity = Partial<{
  8. name: string
  9. type: 'file' | 'folder'
  10. oldPathname: string
  11. newPathname: string
  12. pathname: string
  13. children: FileTreeEntity[]
  14. operation: DiffOperation
  15. }>
  16. export function reducePathsToTree(
  17. currentFileTree: FileTreeEntity[],
  18. fileObject: FileTreeEntity
  19. ) {
  20. const filePathParts = fileObject?.pathname?.split('/') ?? ''
  21. let currentFileTreeLocation = currentFileTree
  22. for (let index = 0; index < filePathParts.length; index++) {
  23. let fileTreeEntity: FileTreeEntity | null = {}
  24. const pathPart = filePathParts[index]
  25. const isFile = index === filePathParts.length - 1
  26. if (isFile) {
  27. fileTreeEntity = _.clone(fileObject)
  28. fileTreeEntity.name = pathPart
  29. fileTreeEntity.type = 'file'
  30. currentFileTreeLocation.push(fileTreeEntity)
  31. } else {
  32. fileTreeEntity =
  33. _.find(currentFileTreeLocation, entity => entity.name === pathPart) ??
  34. null
  35. if (fileTreeEntity == null) {
  36. fileTreeEntity = {
  37. name: pathPart,
  38. type: 'folder',
  39. children: [],
  40. }
  41. currentFileTreeLocation.push(fileTreeEntity)
  42. }
  43. currentFileTreeLocation = fileTreeEntity.children ?? []
  44. }
  45. }
  46. return currentFileTree
  47. }
  48. export type HistoryDoc = {
  49. pathname: string
  50. name: string
  51. } & Pick<FileTreeEntity, 'operation'>
  52. export type HistoryFileTree = {
  53. docs?: HistoryDoc[]
  54. folders: HistoryFileTree[]
  55. name: string
  56. }
  57. export function fileTreeDiffToFileTreeData(
  58. fileTreeDiff: FileTreeEntity[],
  59. currentFolderName = 'rootFolder' // default value from angular version
  60. ): HistoryFileTree {
  61. const folders: HistoryFileTree[] = []
  62. const docs: HistoryDoc[] = []
  63. for (const file of fileTreeDiff) {
  64. if (file.type === 'file') {
  65. docs.push({
  66. pathname: file.pathname ?? '',
  67. name: file.name ?? '',
  68. operation: file.operation,
  69. })
  70. } else if (file.type === 'folder') {
  71. if (file.children) {
  72. const folder = fileTreeDiffToFileTreeData(file.children, file.name)
  73. folders.push(folder)
  74. }
  75. }
  76. }
  77. return {
  78. docs,
  79. folders,
  80. name: currentFolderName,
  81. }
  82. }
  83. // TODO: refactor the oldPathname/newPathname data
  84. // It's an artifact from the angular version.
  85. // Our API returns `pathname` and `newPathname` for `renamed` operation
  86. // In the angular version, we change the key of the data:
  87. // 1. `pathname` -> `oldPathname`
  88. // 2. `newPathname` -> `pathname`
  89. // 3. Delete the `newPathname` key from the object
  90. // This is because the angular version wants to generalize the API usage
  91. // In the operation other than the `renamed` operation, the diff API (/project/:id/diff) consumes the `pathname`
  92. // But the `renamed` operation consumes the `newPathname` instead of the `pathname` data
  93. //
  94. // This behaviour can be refactored by introducing a conditional when calling the API
  95. // i.e if `renamed` -> use `newPathname`, else -> use `pathname`
  96. export function renamePathnameKey(file: FileRenamed): FileRenamed {
  97. return {
  98. oldPathname: file.pathname,
  99. pathname: file.newPathname as string,
  100. operation: file.operation,
  101. }
  102. }
  103. export function isFileRenamed(fileDiff: FileDiff): fileDiff is FileRenamed {
  104. return (fileDiff as FileRenamed).operation === 'renamed'
  105. }