afterToggleCheckbox.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. /* eslint-disable no-param-reassign */
  2. import type {
  3. PartialTree,
  4. PartialTreeFile,
  5. PartialTreeFolder,
  6. PartialTreeFolderNode,
  7. PartialTreeId,
  8. } from '@uppy/core/lib/Uppy'
  9. import shallowClone from './shallowClone.ts'
  10. /*
  11. FROM | TO
  12. root | root
  13. folder | folder
  14. folder ✅︎ | folder ✅︎
  15. file | file ✅︎
  16. file | file ✅︎
  17. folder | folder ✅︎
  18. file | file ✅︎
  19. file | file
  20. file | file
  21. */
  22. const percolateDown = (
  23. tree: PartialTree,
  24. id: PartialTreeId,
  25. shouldMarkAsChecked: boolean,
  26. ) => {
  27. const children = tree.filter(
  28. (item) => item.type !== 'root' && item.parentId === id,
  29. ) as (PartialTreeFolderNode | PartialTreeFile)[]
  30. children.forEach((item) => {
  31. item.status =
  32. shouldMarkAsChecked && !(item.type === 'file' && item.restrictionError) ?
  33. 'checked'
  34. : 'unchecked'
  35. percolateDown(tree, item.id, shouldMarkAsChecked)
  36. })
  37. }
  38. /*
  39. FROM | TO
  40. root | root
  41. folder | folder
  42. folder | folder [▬] ('partial' status)
  43. file | file
  44. folder | folder ✅︎
  45. file ✅︎ | file ✅︎
  46. file | file
  47. file | file
  48. */
  49. const percolateUp = (tree: PartialTree, id: PartialTreeId) => {
  50. const folder = tree.find((item) => item.id === id) as PartialTreeFolder
  51. if (folder.type === 'root') return
  52. const validChildren = tree.filter(
  53. (item) =>
  54. // is a child
  55. item.type !== 'root' &&
  56. item.parentId === folder.id &&
  57. // does pass validations
  58. !(item.type === 'file' && item.restrictionError),
  59. ) as (PartialTreeFile | PartialTreeFolderNode)[]
  60. const areAllChildrenChecked = validChildren.every(
  61. (item) => item.status === 'checked',
  62. )
  63. const areAllChildrenUnchecked = validChildren.every(
  64. (item) => item.status === 'unchecked',
  65. )
  66. if (areAllChildrenChecked) {
  67. folder.status = 'checked'
  68. } else if (areAllChildrenUnchecked) {
  69. folder.status = 'unchecked'
  70. } else {
  71. folder.status = 'partial'
  72. }
  73. percolateUp(tree, folder.parentId)
  74. }
  75. const afterToggleCheckbox = (
  76. oldTree: PartialTree,
  77. clickedRange: string[],
  78. ): PartialTree => {
  79. const tree: PartialTree = shallowClone(oldTree)
  80. if (clickedRange.length >= 2) {
  81. // We checked two or more items
  82. const newlyCheckedItems = tree.filter(
  83. (item) => item.type !== 'root' && clickedRange.includes(item.id),
  84. ) as (PartialTreeFile | PartialTreeFolderNode)[]
  85. newlyCheckedItems.forEach((item) => {
  86. if (item.type === 'file') {
  87. item.status = item.restrictionError ? 'unchecked' : 'checked'
  88. } else {
  89. item.status = 'checked'
  90. }
  91. })
  92. newlyCheckedItems.forEach((item) => {
  93. percolateDown(tree, item.id, true)
  94. })
  95. percolateUp(tree, newlyCheckedItems[0].parentId)
  96. } else {
  97. // We checked exactly one item
  98. const clickedItem = tree.find((item) => item.id === clickedRange[0]) as
  99. | PartialTreeFile
  100. | PartialTreeFolderNode
  101. clickedItem.status =
  102. clickedItem.status === 'checked' ? 'unchecked' : 'checked'
  103. percolateDown(tree, clickedItem.id, clickedItem.status === 'checked')
  104. percolateUp(tree, clickedItem.parentId)
  105. }
  106. return tree
  107. }
  108. export default afterToggleCheckbox