selectedGroupStore.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import {createStore} from 'reflux';
  2. import GroupStore from 'sentry/stores/groupStore';
  3. import type {CommonStoreDefinition} from './types';
  4. interface InternalDefinition {
  5. /**
  6. * The last item to have been selected
  7. */
  8. lastSelected: string | null;
  9. /**
  10. * Mapping of item ID -> if it is selected. This is a map to
  11. * make it easier to track if everything has been selected or not.
  12. */
  13. records: Map<string, boolean>;
  14. }
  15. interface SelectedGroupStoreDefinition
  16. extends CommonStoreDefinition<Record<string, boolean>>,
  17. InternalDefinition {
  18. add(ids: string[]): void;
  19. allSelected(): boolean;
  20. anySelected(): boolean;
  21. deselectAll(): void;
  22. getSelectedIds(): Set<string>;
  23. init(): void;
  24. isSelected(itemId: string): boolean;
  25. multiSelected(): boolean;
  26. onGroupChange(itemIds: Set<string>): void;
  27. prune(): void;
  28. reset(): void;
  29. shiftToggleItems(itemId: string): void;
  30. toggleSelect(itemId: string): void;
  31. toggleSelectAll(): void;
  32. }
  33. const storeConfig: SelectedGroupStoreDefinition = {
  34. records: new Map(),
  35. lastSelected: null,
  36. unsubscribeListeners: [],
  37. init() {
  38. // XXX: Do not use `this.listenTo` in this store. We avoid usage of reflux
  39. // listeners due to their leaky nature in tests.
  40. this.reset();
  41. },
  42. reset() {
  43. this.records = new Map();
  44. this.lastSelected = null;
  45. },
  46. getState() {
  47. return Object.fromEntries(this.records);
  48. },
  49. onGroupChange(itemIds) {
  50. this.prune();
  51. this.add([...itemIds]);
  52. this.trigger();
  53. },
  54. add(ids) {
  55. const allSelected = this.allSelected();
  56. ids
  57. .filter(id => !this.records.has(id))
  58. .forEach(id => this.records.set(id, allSelected));
  59. },
  60. prune() {
  61. const existingIds = new Set(GroupStore.getAllItemIds());
  62. this.lastSelected = null;
  63. // Remove everything that no longer exists
  64. [...this.records.keys()]
  65. .filter(id => !existingIds.has(id))
  66. .forEach(id => this.records.delete(id));
  67. },
  68. allSelected() {
  69. const itemIds = this.getSelectedIds();
  70. return itemIds.size > 0 && itemIds.size === this.records.size;
  71. },
  72. numSelected() {
  73. return this.getSelectedIds().size;
  74. },
  75. anySelected() {
  76. return this.getSelectedIds().size > 0;
  77. },
  78. multiSelected() {
  79. return this.getSelectedIds().size > 1;
  80. },
  81. getSelectedIds() {
  82. return new Set([...this.records.keys()].filter(id => this.records.get(id)));
  83. },
  84. isSelected(itemId) {
  85. return !!this.records.get(itemId);
  86. },
  87. deselectAll() {
  88. this.records.forEach((_, id) => this.records.set(id, false));
  89. this.trigger();
  90. },
  91. toggleSelect(itemId) {
  92. if (!this.records.has(itemId)) {
  93. return;
  94. }
  95. const newState = !this.records.get(itemId);
  96. this.records.set(itemId, newState);
  97. this.lastSelected = itemId;
  98. this.trigger();
  99. },
  100. toggleSelectAll() {
  101. const allSelected = !this.allSelected();
  102. this.lastSelected = null;
  103. this.records.forEach((_, id) => this.records.set(id, allSelected));
  104. this.trigger();
  105. },
  106. shiftToggleItems(itemId) {
  107. if (!this.records.has(itemId)) {
  108. return;
  109. }
  110. if (!this.lastSelected) {
  111. this.toggleSelect(itemId);
  112. return;
  113. }
  114. const ids = GroupStore.getAllItemIds();
  115. const lastIdx = ids.findIndex(id => id === this.lastSelected);
  116. const currentIdx = ids.findIndex(id => id === itemId);
  117. if (lastIdx === -1 || currentIdx === -1) {
  118. return;
  119. }
  120. const newValue = !this.records.get(itemId);
  121. const selected =
  122. lastIdx < currentIdx
  123. ? ids.slice(lastIdx, currentIdx)
  124. : ids.slice(currentIdx, lastIdx);
  125. [...selected, this.lastSelected, itemId]
  126. .filter(id => this.records.has(id))
  127. .forEach(id => this.records.set(id, newValue));
  128. this.lastSelected = itemId;
  129. this.trigger();
  130. },
  131. };
  132. const SelectedGroupStore = createStore(storeConfig);
  133. export default SelectedGroupStore;