Skip to content

Commit

Permalink
feat(spans): Allow horizontal scrolling on left pane of span view (#3…
Browse files Browse the repository at this point in the history
…4006)

Co-authored-by: Kev <6111995+k-fish@users.noreply.github.com>
Co-authored-by: Ash Anand <0Calories@users.noreply.github.com>
Co-authored-by: Ash Anand <ash.anand@sentry.io>
  • Loading branch information
4 people authored May 12, 2022
1 parent ad8fec6 commit 1894ff2
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 134 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type ScrollbarManagerChildrenProps = {
generateContentSpanBarRef: () => (instance: HTMLDivElement | null) => void;
onDragStart: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
onScroll: () => void;
onWheel: (deltaX: number) => void;
scrollBarAreaRef: React.RefObject<HTMLDivElement>;
updateScrollState: () => void;
virtualScrollbarRef: React.RefObject<HTMLDivElement>;
Expand All @@ -25,6 +26,7 @@ const ScrollbarManagerContext = createContext<ScrollbarManagerChildrenProps>({
scrollBarAreaRef: createRef<HTMLDivElement>(),
onDragStart: () => {},
onScroll: () => {},
onWheel: () => {},
updateScrollState: () => {},
});

Expand Down Expand Up @@ -105,6 +107,8 @@ export class Provider extends Component<Props, State> {
virtualScrollbar: React.RefObject<HTMLDivElement> = createRef<HTMLDivElement>();
scrollBarArea: React.RefObject<HTMLDivElement> = createRef<HTMLDivElement>();
isDragging: boolean = false;
isWheeling: boolean = false;
wheelTimeout: NodeJS.Timeout | null = null;
previousUserSelect: UserSelectValues | null = null;

getReferenceSpanBar() {
Expand Down Expand Up @@ -233,11 +237,70 @@ export class Provider extends Component<Props, State> {
hasInteractiveLayer = (): boolean => !!this.props.interactiveLayerRef.current;
initialMouseClickX: number | undefined = undefined;

onScroll = () => {
onWheel = (deltaX: number) => {
if (this.isDragging || !this.hasInteractiveLayer()) {
return;
}

// Setting this here is necessary, since updating the virtual scrollbar position will also trigger the onScroll function
this.isWheeling = true;

if (this.wheelTimeout) {
clearTimeout(this.wheelTimeout);
}

this.wheelTimeout = setTimeout(() => {
this.isWheeling = false;
this.wheelTimeout = null;
}, 200);

const interactiveLayerRefDOM = this.props.interactiveLayerRef.current!;

const interactiveLayerRect = interactiveLayerRefDOM.getBoundingClientRect();
const maxScrollLeft =
interactiveLayerRefDOM.scrollWidth - interactiveLayerRefDOM.clientWidth;

const scrollLeft = clamp(
interactiveLayerRefDOM.scrollLeft + deltaX,
0,
maxScrollLeft
);

interactiveLayerRefDOM.scrollLeft = scrollLeft;

// Update scroll position of the virtual scroll bar
selectRefs(this.scrollBarArea, (scrollBarAreaDOM: HTMLDivElement) => {
selectRefs(this.virtualScrollbar, (virtualScrollbarDOM: HTMLDivElement) => {
const scrollBarAreaRect = scrollBarAreaDOM.getBoundingClientRect();
const virtualScrollbarPosition = scrollLeft / scrollBarAreaRect.width;

const virtualScrollBarRect = rectOfContent(virtualScrollbarDOM);
const maxVirtualScrollableArea =
1 - virtualScrollBarRect.width / interactiveLayerRect.width;

const virtualLeft =
clamp(virtualScrollbarPosition, 0, maxVirtualScrollableArea) *
interactiveLayerRect.width;

virtualScrollbarDOM.style.transform = `translate3d(${virtualLeft}px, 0, 0)`;
virtualScrollbarDOM.style.transformOrigin = 'left';
});
});

// Update scroll positions of all the span bars
selectRefs(this.contentSpanBar, (spanBarDOM: HTMLDivElement) => {
const left = -scrollLeft;

spanBarDOM.style.transform = `translate3d(${left}px, 0, 0)`;
spanBarDOM.style.transformOrigin = 'left';
});
};

onScroll = () => {
if (this.isDragging || this.isWheeling || !this.hasInteractiveLayer()) {
return;
}

const interactiveLayerRefDOM = this.props.interactiveLayerRef.current!;

const interactiveLayerRect = interactiveLayerRefDOM.getBoundingClientRect();
Expand Down Expand Up @@ -415,6 +478,7 @@ export class Provider extends Component<Props, State> {
generateContentSpanBarRef: this.generateContentSpanBarRef,
onDragStart: this.onDragStart,
onScroll: this.onScroll,
onWheel: this.onWheel,
virtualScrollbarRef: this.virtualScrollbar,
scrollBarAreaRef: this.scrollBarArea,
updateScrollState: this.initializeScrollState,
Expand Down
67 changes: 42 additions & 25 deletions static/app/components/events/interfaces/spans/spanBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ import {
NUM_OF_SPANS_FIT_IN_MINI_MAP,
} from './constants';
import * as DividerHandlerManager from './dividerHandlerManager';
import * as ScrollbarManager from './scrollbarManager';
import SpanBarCursorGuide from './spanBarCursorGuide';
import SpanDetail from './spanDetail';
import {MeasurementMarker} from './styles';
Expand Down Expand Up @@ -108,9 +107,11 @@ type SpanBarProps = {
event: Readonly<EventTransaction>;
fetchEmbeddedChildrenState: FetchEmbeddedChildrenState;
generateBounds: (bounds: SpanBoundsType) => SpanGeneratedBoundsType;
generateContentSpanBarRef: () => (instance: HTMLDivElement | null) => void;
isEmbeddedTransactionTimeAdjusted: boolean;
numOfSpanChildren: number;
numOfSpans: number;
onWheel: (deltaX: number) => void;
organization: Organization;
showEmbeddedChildren: boolean;
showSpanTree: boolean;
Expand Down Expand Up @@ -146,18 +147,42 @@ class SpanBar extends Component<SpanBarProps, SpanBarState> {
if (this.spanRowDOMRef.current) {
this.connectObservers();
}

if (this.spanTitleRef.current) {
this.spanTitleRef.current.addEventListener('wheel', this.handleWheel, {
passive: false,
});
}
}

componentWillUnmount() {
this._mounted = false;
this.disconnectObservers();

if (this.spanTitleRef.current) {
this.spanTitleRef.current.removeEventListener('wheel', this.handleWheel);
}
}

spanRowDOMRef = createRef<HTMLDivElement>();
spanTitleRef = createRef<HTMLDivElement>();
intersectionObserver?: IntersectionObserver = void 0;
zoomLevel: number = 1; // assume initial zoomLevel is 100%
_mounted: boolean = false;

handleWheel = (event: WheelEvent) => {
// https://stackoverflow.com/q/57358640
// https://github.com/facebook/react/issues/14856
if (Math.abs(event.deltaY) > Math.abs(event.deltaX)) {
return;
}

event.preventDefault();
event.stopPropagation();
const {onWheel} = this.props;
onWheel(event.deltaX);
};

toggleDisplayDetail = () => {
this.setState(state => ({
showDetail: !state.showDetail,
Expand Down Expand Up @@ -427,11 +452,8 @@ class SpanBar extends Component<SpanBarProps, SpanBarState> {
);
}

renderTitle(
scrollbarManagerChildrenProps: ScrollbarManager.ScrollbarManagerChildrenProps,
errors: TraceError[] | null
) {
const {generateContentSpanBarRef} = scrollbarManagerChildrenProps;
renderTitle(errors: TraceError[] | null) {
const {generateContentSpanBarRef} = this.props;
const {
span,
treeDepth,
Expand Down Expand Up @@ -834,14 +856,12 @@ class SpanBar extends Component<SpanBarProps, SpanBarState> {
}

renderHeader({
scrollbarManagerChildrenProps,
dividerHandlerChildrenProps,
errors,
transactions,
}: {
dividerHandlerChildrenProps: DividerHandlerManager.DividerHandlerManagerChildrenProps;
errors: TraceError[] | null;
scrollbarManagerChildrenProps: ScrollbarManager.ScrollbarManagerChildrenProps;
transactions: QuickTraceEvent[] | null;
}) {
const {span, spanBarColor, spanBarHatch, spanNumber} = this.props;
Expand All @@ -866,8 +886,9 @@ class SpanBar extends Component<SpanBarProps, SpanBarState> {
onClick={() => {
this.toggleDisplayDetail();
}}
ref={this.spanTitleRef}
>
{this.renderTitle(scrollbarManagerChildrenProps, errors)}
{this.renderTitle(errors)}
</RowCell>
<DividerContainer>
{this.renderDivider(dividerHandlerChildrenProps)}
Expand Down Expand Up @@ -975,22 +996,18 @@ class SpanBar extends Component<SpanBarProps, SpanBarState> {
const transactions = this.getChildTransactions(quickTrace);
return (
<Fragment>
<ScrollbarManager.Consumer>
{scrollbarManagerChildrenProps => (
<DividerHandlerManager.Consumer>
{(
dividerHandlerChildrenProps: DividerHandlerManager.DividerHandlerManagerChildrenProps
) =>
this.renderHeader({
dividerHandlerChildrenProps,
scrollbarManagerChildrenProps,
errors,
transactions,
})
}
</DividerHandlerManager.Consumer>
)}
</ScrollbarManager.Consumer>
<DividerHandlerManager.Consumer>
{(
dividerHandlerChildrenProps: DividerHandlerManager.DividerHandlerManagerChildrenProps
) =>
this.renderHeader({
dividerHandlerChildrenProps,
errors,
transactions,
})
}
</DividerHandlerManager.Consumer>

{this.renderDetail({
isVisible: isSpanVisibleInView,
transactions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type Props = {
continuingTreeDepths: Array<TreeDepthType>;
event: Readonly<EventTransaction>;
generateBounds: (bounds: SpanBoundsType) => SpanGeneratedBoundsType;
generateContentSpanBarRef: () => (instance: HTMLDivElement | null) => void;
onWheel: (deltaX: number) => void;
span: Readonly<ProcessedSpanType>;
spanGrouping: EnhancedSpan[];
spanNumber: number;
Expand All @@ -49,6 +51,8 @@ export function SpanDescendantGroupBar(props: Props) {
spanGrouping,
spanNumber,
toggleSpanGroup,
onWheel,
generateContentSpanBarRef,
} = props;

function renderGroupSpansTitle() {
Expand Down Expand Up @@ -161,6 +165,8 @@ export function SpanDescendantGroupBar(props: Props) {
renderSpanTreeConnector={renderSpanTreeConnector}
renderGroupSpansTitle={renderGroupSpansTitle}
renderSpanRectangles={renderSpanRectangles}
onWheel={onWheel}
generateContentSpanBarRef={generateContentSpanBarRef}
/>
);
}
Loading

0 comments on commit 1894ff2

Please sign in to comment.