feedbackList.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import {Fragment, useEffect, useMemo, useRef} from 'react';
  2. import {
  3. AutoSizer,
  4. CellMeasurer,
  5. InfiniteLoader,
  6. List as ReactVirtualizedList,
  7. ListRowProps,
  8. } from 'react-virtualized';
  9. import styled from '@emotion/styled';
  10. import {useInfiniteFeedbackListData} from 'sentry/components/feedback/feedbackDataContext';
  11. import FeedbackListHeader from 'sentry/components/feedback/list/feedbackListHeader';
  12. import FeedbackListItem from 'sentry/components/feedback/list/feedbackListItem';
  13. import useListItemCheckboxState from 'sentry/components/feedback/list/useListItemCheckboxState';
  14. import LoadingIndicator from 'sentry/components/loadingIndicator';
  15. import PanelItem from 'sentry/components/panels/panelItem';
  16. import {Tooltip} from 'sentry/components/tooltip';
  17. import {t} from 'sentry/locale';
  18. import useUrlParams from 'sentry/utils/useUrlParams';
  19. import NoRowRenderer from 'sentry/views/replays/detail/noRowRenderer';
  20. import useVirtualizedList from 'sentry/views/replays/detail/useVirtualizedList';
  21. // Ensure this object is created once as it is an input to
  22. // `useVirtualizedList`'s memoization
  23. const cellMeasurer = {
  24. fixedWidth: true,
  25. minHeight: 24,
  26. };
  27. export default function FeedbackList() {
  28. const {
  29. // error,
  30. // hasNextPage,
  31. // isError,
  32. isFetching, // If the network is active
  33. isFetchingNextPage,
  34. isFetchingPreviousPage,
  35. isLoading, // If anything is loaded yet
  36. // Below are fields that are shims for react-virtualized
  37. getRow,
  38. isRowLoaded,
  39. issues,
  40. loadMoreRows,
  41. // setFeedback,
  42. } = useInfiniteFeedbackListData();
  43. const {setParamValue} = useUrlParams('query');
  44. const clearSearchTerm = () => setParamValue('');
  45. const {checked, toggleChecked} = useListItemCheckboxState();
  46. const listRef = useRef<ReactVirtualizedList>(null);
  47. const hasRows = !isLoading;
  48. const deps = useMemo(() => [hasRows], [hasRows]);
  49. const {cache, updateList} = useVirtualizedList({
  50. cellMeasurer,
  51. ref: listRef,
  52. deps,
  53. });
  54. useEffect(() => {
  55. updateList();
  56. }, [updateList, issues.length]);
  57. const renderRow = ({index, key, style, parent}: ListRowProps) => {
  58. const item = getRow({index});
  59. if (!item) {
  60. return null;
  61. }
  62. return (
  63. <CellMeasurer
  64. cache={cache}
  65. columnIndex={0}
  66. key={key}
  67. parent={parent}
  68. rowIndex={index}
  69. >
  70. <FeedbackListItem
  71. feedbackItem={item}
  72. style={style}
  73. isChecked={checked.includes(item.id)}
  74. onChecked={() => {
  75. toggleChecked(item.id);
  76. }}
  77. />
  78. </CellMeasurer>
  79. );
  80. };
  81. return (
  82. <Fragment>
  83. <FeedbackListHeader checked={checked} toggleChecked={toggleChecked} />
  84. <OverflowPanelItem noPadding>
  85. <InfiniteLoader
  86. isRowLoaded={isRowLoaded}
  87. loadMoreRows={loadMoreRows}
  88. rowCount={issues.length}
  89. >
  90. {({onRowsRendered, registerChild}) => (
  91. <AutoSizer onResize={updateList}>
  92. {({width, height}) => (
  93. <ReactVirtualizedList
  94. deferredMeasurementCache={cache}
  95. height={height}
  96. noRowsRenderer={() =>
  97. isFetching ? (
  98. <LoadingIndicator />
  99. ) : (
  100. <NoRowRenderer
  101. unfilteredItems={issues}
  102. clearSearchTerm={clearSearchTerm}
  103. >
  104. {t('No feedback received')}
  105. </NoRowRenderer>
  106. )
  107. }
  108. onRowsRendered={onRowsRendered}
  109. overscanRowCount={5}
  110. ref={registerChild}
  111. rowCount={issues.length}
  112. rowHeight={cache.rowHeight}
  113. rowRenderer={renderRow}
  114. width={width}
  115. />
  116. )}
  117. </AutoSizer>
  118. )}
  119. </InfiniteLoader>
  120. <FloatingContainer style={{top: '2px'}}>
  121. {isFetchingPreviousPage ? (
  122. <Tooltip title={t('Loading more feedback...')}>
  123. <LoadingIndicator mini />
  124. </Tooltip>
  125. ) : null}
  126. </FloatingContainer>
  127. <FloatingContainer style={{bottom: '2px'}}>
  128. {isFetchingNextPage ? (
  129. <Tooltip title={t('Loading more feedback...')}>
  130. <LoadingIndicator mini />
  131. </Tooltip>
  132. ) : null}
  133. </FloatingContainer>
  134. </OverflowPanelItem>
  135. </Fragment>
  136. );
  137. }
  138. const OverflowPanelItem = styled(PanelItem)`
  139. display: grid;
  140. overflow: scroll;
  141. flex-grow: 1;
  142. `;
  143. const FloatingContainer = styled('div')`
  144. position: absolute;
  145. justify-self: center;
  146. `;