|
@@ -1,57 +1,148 @@
|
|
|
import React from 'react';
|
|
|
import styled from '@emotion/styled';
|
|
|
+import {
|
|
|
+ List,
|
|
|
+ ListRowProps,
|
|
|
+ CellMeasurer,
|
|
|
+ CellMeasurerCache,
|
|
|
+ AutoSizer,
|
|
|
+} from 'react-virtualized';
|
|
|
|
|
|
-import space from 'app/styles/space';
|
|
|
-
|
|
|
+import {aroundContentStyle} from './styles';
|
|
|
import ListHeader from './listHeader';
|
|
|
import ListBody from './listBody';
|
|
|
-import {aroundContentStyle} from './styles';
|
|
|
+import {BreadcrumbsWithDetails} from './types';
|
|
|
+
|
|
|
+const LIST_MAX_HEIGHT = 400;
|
|
|
|
|
|
type Props = {
|
|
|
onSwitchTimeFormat: () => void;
|
|
|
-} & Omit<React.ComponentProps<typeof ListBody>, 'relativeTime'>;
|
|
|
-
|
|
|
-const List = React.forwardRef(
|
|
|
- (
|
|
|
- {
|
|
|
- displayRelativeTime,
|
|
|
- onSwitchTimeFormat,
|
|
|
- orgId,
|
|
|
- event,
|
|
|
- breadcrumbs,
|
|
|
- searchTerm,
|
|
|
- }: Props,
|
|
|
- ref: React.Ref<HTMLDivElement>
|
|
|
- ) => (
|
|
|
- <Grid ref={ref}>
|
|
|
- <ListHeader
|
|
|
- onSwitchTimeFormat={onSwitchTimeFormat}
|
|
|
- displayRelativeTime={!!displayRelativeTime}
|
|
|
- />
|
|
|
+ breadcrumbs: BreadcrumbsWithDetails;
|
|
|
+ relativeTime: string;
|
|
|
+} & Omit<React.ComponentProps<typeof ListBody>, 'breadcrumb' | 'isLastItem' | 'column'>;
|
|
|
+
|
|
|
+const cache = new CellMeasurerCache({
|
|
|
+ fixedWidth: true,
|
|
|
+ minHeight: 42,
|
|
|
+});
|
|
|
+
|
|
|
+class ListContainer extends React.Component<Props> {
|
|
|
+ componentDidMount() {
|
|
|
+ this.updateGrid();
|
|
|
+ }
|
|
|
+
|
|
|
+ componentDidUpdate() {
|
|
|
+ this.updateGrid();
|
|
|
+ }
|
|
|
+
|
|
|
+ listRef: List | null = null;
|
|
|
+
|
|
|
+ updateGrid = () => {
|
|
|
+ if (this.listRef) {
|
|
|
+ cache.clearAll();
|
|
|
+ this.listRef.forceUpdateGrid();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ renderBody(breadcrumb: BreadcrumbsWithDetails[0], isLastItem = false) {
|
|
|
+ const {event, orgId, searchTerm, relativeTime, displayRelativeTime} = this.props;
|
|
|
+ return (
|
|
|
<ListBody
|
|
|
+ orgId={orgId}
|
|
|
searchTerm={searchTerm}
|
|
|
+ breadcrumb={breadcrumb}
|
|
|
event={event}
|
|
|
- orgId={orgId}
|
|
|
- breadcrumbs={breadcrumbs}
|
|
|
- relativeTime={breadcrumbs[breadcrumbs.length - 1]?.timestamp}
|
|
|
- displayRelativeTime={!!displayRelativeTime}
|
|
|
+ relativeTime={relativeTime}
|
|
|
+ displayRelativeTime={displayRelativeTime}
|
|
|
+ isLastItem={isLastItem}
|
|
|
/>
|
|
|
- </Grid>
|
|
|
- )
|
|
|
-);
|
|
|
+ );
|
|
|
+ }
|
|
|
|
|
|
-export default List;
|
|
|
+ renderRow = ({index, key, parent, style}: ListRowProps) => {
|
|
|
+ const {breadcrumbs} = this.props;
|
|
|
+ const breadcrumb = breadcrumbs[index];
|
|
|
+ const isLastItem = breadcrumbs[breadcrumbs.length - 1].id === breadcrumb.id;
|
|
|
+ return (
|
|
|
+ <CellMeasurer
|
|
|
+ cache={cache}
|
|
|
+ columnIndex={0}
|
|
|
+ key={key}
|
|
|
+ parent={parent}
|
|
|
+ rowIndex={index}
|
|
|
+ >
|
|
|
+ {({measure}) =>
|
|
|
+ isLastItem ? (
|
|
|
+ <Row style={style} onLoad={measure} data-test-id="last-crumb">
|
|
|
+ {this.renderBody(breadcrumb, isLastItem)}
|
|
|
+ </Row>
|
|
|
+ ) : (
|
|
|
+ <Row style={style} onLoad={measure}>
|
|
|
+ {this.renderBody(breadcrumb)}
|
|
|
+ </Row>
|
|
|
+ )
|
|
|
+ }
|
|
|
+ </CellMeasurer>
|
|
|
+ );
|
|
|
+ };
|
|
|
|
|
|
-const Grid = styled('div')`
|
|
|
- max-height: 500px;
|
|
|
- overflow-y: auto;
|
|
|
- display: grid;
|
|
|
- > *:nth-last-child(5):before {
|
|
|
- bottom: calc(100% - ${space(1)});
|
|
|
+ render() {
|
|
|
+ const {breadcrumbs, displayRelativeTime, onSwitchTimeFormat} = this.props;
|
|
|
+
|
|
|
+ // onResize is required in case the user rotates the device.
|
|
|
+ return (
|
|
|
+ <Wrapper>
|
|
|
+ <AutoSizer disableHeight onResize={this.updateGrid}>
|
|
|
+ {({width}) => (
|
|
|
+ <React.Fragment>
|
|
|
+ <Row width={width}>
|
|
|
+ <ListHeader
|
|
|
+ displayRelativeTime={!!displayRelativeTime}
|
|
|
+ onSwitchTimeFormat={onSwitchTimeFormat}
|
|
|
+ />
|
|
|
+ </Row>
|
|
|
+ <StyledList
|
|
|
+ ref={(el: List | null) => {
|
|
|
+ this.listRef = el;
|
|
|
+ }}
|
|
|
+ deferredMeasurementCache={cache}
|
|
|
+ height={LIST_MAX_HEIGHT}
|
|
|
+ overscanRowCount={5}
|
|
|
+ rowCount={breadcrumbs.length}
|
|
|
+ rowHeight={cache.rowHeight}
|
|
|
+ rowRenderer={this.renderRow}
|
|
|
+ width={width}
|
|
|
+ // when the component mounts, it scrolls to the last item
|
|
|
+ scrollToIndex={breadcrumbs.length - 1}
|
|
|
+ scrollToAlignment="end"
|
|
|
+ />
|
|
|
+ </React.Fragment>
|
|
|
+ )}
|
|
|
+ </AutoSizer>
|
|
|
+ </Wrapper>
|
|
|
+ );
|
|
|
}
|
|
|
- grid-template-columns: max-content minmax(55px, 1fr) 6fr max-content 65px;
|
|
|
+}
|
|
|
+
|
|
|
+export default ListContainer;
|
|
|
+
|
|
|
+const Wrapper = styled('div')`
|
|
|
+ overflow: hidden;
|
|
|
+ ${aroundContentStyle}
|
|
|
+`;
|
|
|
+
|
|
|
+// it makes the list have a dynamic height; otherwise, in the case of filtered options, a list will be displayed with an empty space
|
|
|
+const StyledList = styled(List)<{height: number}>`
|
|
|
+ height: auto !important;
|
|
|
+ max-height: ${p => p.height}px;
|
|
|
+ overflow-y: auto !important;
|
|
|
+`;
|
|
|
+
|
|
|
+const Row = styled('div')<{width?: number}>`
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 45px minmax(55px, 1fr) 6fr 86px 67px;
|
|
|
@media (min-width: ${p => p.theme.breakpoints[0]}) {
|
|
|
- grid-template-columns: max-content minmax(132px, 1fr) 6fr max-content max-content;
|
|
|
+ grid-template-columns: 63px minmax(132px, 1fr) 6fr 75px 85px;
|
|
|
}
|
|
|
- ${aroundContentStyle}
|
|
|
+ ${p => p.width && `width: ${p.width}px`};
|
|
|
`;
|