selectedGroupStore.tsx 4.3 KB

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