arrayMapper.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import { arrayReduce, arrayMap, arrayMax } from './../helpers/array';
  2. import { defineGetter } from './../helpers/object';
  3. import { rangeEach } from './../helpers/number';
  4. const MIXIN_NAME = 'arrayMapper';
  5. /**
  6. * @type {Object}
  7. */
  8. const arrayMapper = {
  9. _arrayMap: [],
  10. /**
  11. * Get translated index by its physical index.
  12. *
  13. * @param {Number} physicalIndex Physical index.
  14. * @return {Number|null} Returns translated index mapped by passed physical index.
  15. */
  16. getValueByIndex(physicalIndex) {
  17. const length = this._arrayMap.length;
  18. let translatedIndex = null;
  19. if (physicalIndex < length) {
  20. translatedIndex = this._arrayMap[physicalIndex];
  21. }
  22. return translatedIndex;
  23. },
  24. /**
  25. * Get physical index by its translated index.
  26. *
  27. * @param {*} translatedIndex Value to search.
  28. * @returns {Number|null} Returns a physical index of the array mapper.
  29. */
  30. getIndexByValue(translatedIndex) {
  31. let physicalIndex;
  32. // eslint-disable-next-line no-cond-assign, no-return-assign
  33. return (physicalIndex = this._arrayMap.indexOf(translatedIndex)) === -1 ? null : physicalIndex;
  34. },
  35. /**
  36. * Insert new items to array mapper starting at passed index. New entries will be a continuation of last value in the array.
  37. *
  38. * @param {Number} physicalIndex Array index.
  39. * @param {Number} [amount=1] Defines how many items will be created to an array.
  40. * @returns {Array} Returns added items.
  41. */
  42. insertItems(physicalIndex, amount = 1) {
  43. const newIndex = arrayMax(this._arrayMap) + 1;
  44. const addedItems = [];
  45. rangeEach(amount - 1, (count) => {
  46. addedItems.push(this._arrayMap.splice(physicalIndex + count, 0, newIndex + count));
  47. });
  48. return addedItems;
  49. },
  50. /**
  51. * Remove items from array mapper.
  52. *
  53. * @param {Number} physicalIndex Array index.
  54. * @param {Number} [amount=1] Defines how many items will be created to an array.
  55. * @returns {Array} Returns removed items.
  56. */
  57. removeItems(physicalIndex, amount = 1) {
  58. let removedItems = [];
  59. if (Array.isArray(physicalIndex)) {
  60. const mapCopy = [].concat(this._arrayMap);
  61. // Sort descending
  62. physicalIndex.sort((a, b) => b - a);
  63. for (let i = 0, length = physicalIndex.length; i < length; i++) {
  64. const indexToRemove = physicalIndex[i];
  65. this._arrayMap.splice(indexToRemove, 1);
  66. removedItems.push(mapCopy[indexToRemove]);
  67. }
  68. } else {
  69. removedItems = this._arrayMap.splice(physicalIndex, amount);
  70. }
  71. return removedItems;
  72. },
  73. /**
  74. * Unshift items (remove and shift chunk of array to the left).
  75. *
  76. * @param {Number|Array} physicalIndex Array index or Array of indexes to unshift.
  77. * @param {Number} [amount=1] Defines how many items will be removed from an array (when index is passed as number).
  78. */
  79. unshiftItems(physicalIndex, amount = 1) {
  80. const removedItems = this.removeItems(physicalIndex, amount);
  81. function countRowShift(logicalRow) {
  82. // Todo: compare perf between reduce vs sort->each->brake
  83. return arrayReduce(removedItems, (count, removedLogicalRow) => {
  84. let result = count;
  85. if (logicalRow > removedLogicalRow) {
  86. result += 1;
  87. }
  88. return result;
  89. }, 0);
  90. }
  91. this._arrayMap = arrayMap(this._arrayMap, (logicalRow) => {
  92. let logicalRowIndex = logicalRow;
  93. const rowShift = countRowShift(logicalRowIndex);
  94. if (rowShift) {
  95. logicalRowIndex -= rowShift;
  96. }
  97. return logicalRowIndex;
  98. });
  99. },
  100. /**
  101. * Shift (right shifting) items starting at passed index.
  102. *
  103. * @param {Number} physicalIndex Array index.
  104. * @param {Number} [amount=1] Defines how many items will be created to an array.
  105. */
  106. shiftItems(physicalIndex, amount = 1) {
  107. this._arrayMap = arrayMap(this._arrayMap, (row) => {
  108. let physicalRowIndex = row;
  109. if (physicalRowIndex >= physicalIndex) {
  110. physicalRowIndex += amount;
  111. }
  112. return physicalRowIndex;
  113. });
  114. rangeEach(amount - 1, (count) => {
  115. this._arrayMap.splice(physicalIndex + count, 0, physicalIndex + count);
  116. });
  117. },
  118. /**
  119. * Swap indexes in arrayMapper.
  120. *
  121. * @param {Number} physicalIndexFrom index to move.
  122. * @param {Number} physicalIndexTo index to.
  123. */
  124. swapIndexes(physicalIndexFrom, physicalIndexTo) {
  125. this._arrayMap.splice(physicalIndexTo, 0, ...this._arrayMap.splice(physicalIndexFrom, 1));
  126. },
  127. /**
  128. * Clear all stored index<->value information from an array.
  129. */
  130. clearMap() {
  131. this._arrayMap.length = 0;
  132. }
  133. };
  134. defineGetter(arrayMapper, 'MIXIN_NAME', MIXIN_NAME, {
  135. writable: false,
  136. enumerable: false,
  137. });
  138. export default arrayMapper;