disambiguate-paths.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import { findInTree } from '@/features/file-tree/util/find-in-tree'
  2. import { Folder } from '@ol-types/folder'
  3. import { FileTreeFindResult } from '../types/file-tree'
  4. type Id = string
  5. type PartialTabInfo = {
  6. id: Id
  7. name: string
  8. path: Id[]
  9. depth: number
  10. }
  11. const lookupNameInTreeWithCache = (
  12. fileTree: Folder,
  13. id: Id,
  14. cache: Map<Id, string>
  15. ) => {
  16. if (id === fileTree._id) return '' // Special case for root
  17. // Check cache first to avoid expensive tree traversal on every call
  18. const cached = cache.get(id)
  19. if (cached !== undefined) {
  20. return cached
  21. }
  22. // Finally look it up if we haven't already seen the id
  23. const result = findInTree(fileTree, id)
  24. const name = result?.entity.name ?? ''
  25. cache.set(id, name)
  26. return name
  27. }
  28. function getDisplayPath(
  29. tab: PartialTabInfo,
  30. fileTree: Folder,
  31. cache: Map<Id, string>
  32. ): string {
  33. return (
  34. tab.path
  35. // filter out the first part of the path (root folder)
  36. .slice(Math.max(tab.path.length - tab.depth, 1), tab.path.length)
  37. .map(parentFolderId =>
  38. lookupNameInTreeWithCache(fileTree, parentFolderId, cache)
  39. )
  40. .concat(tab.name)
  41. .join('/')
  42. )
  43. }
  44. export const disambiguatePaths = (
  45. tabs: FileTreeFindResult[],
  46. fileTree: Folder
  47. ): Map<Id, string> => {
  48. const cache = new Map<Id, string>()
  49. const results = new Map<Id, string>()
  50. const tabsWithPartialPaths: PartialTabInfo[] = tabs.map(tab => ({
  51. id: tab.entity._id,
  52. name: tab.entity.name,
  53. path: tab.path,
  54. depth: 0,
  55. }))
  56. while (true) {
  57. const collisionMap = new Map<string, PartialTabInfo[]>()
  58. for (const tab of tabsWithPartialPaths) {
  59. // already resolved
  60. if (results.has(tab.id)) {
  61. continue
  62. }
  63. const displayPath = getDisplayPath(tab, fileTree, cache)
  64. if (!collisionMap.has(displayPath)) {
  65. collisionMap.set(displayPath, [])
  66. }
  67. collisionMap.get(displayPath)!.push(tab)
  68. }
  69. let hasCollisions = false
  70. for (const [displayPath, collidingTabs] of collisionMap.entries()) {
  71. if (collidingTabs.length === 1) {
  72. const tab = collidingTabs[0]
  73. results.set(tab.id, displayPath)
  74. } else {
  75. for (const tab of collidingTabs) {
  76. if (tab.depth < tab.path.length - 1) {
  77. // This tab is still colliding, but we can reveal more of the path
  78. // (minus root folder) to try to disambiguate it
  79. tab.depth++
  80. hasCollisions = true
  81. } else {
  82. // We've already revealed the entire path and they're still
  83. // colliding, so we'll just have to use the name
  84. results.set(tab.id, tab.name)
  85. }
  86. }
  87. }
  88. }
  89. if (!hasCollisions) {
  90. break
  91. }
  92. }
  93. return results
  94. }