profilingFlamechartLayout.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import {cloneElement, useMemo, useRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import {FlamegraphPreferences} from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/reducers/flamegraphPreferences';
  4. import {FlamegraphTheme} from 'sentry/utils/profiling/flamegraph/flamegraphTheme';
  5. import {useFlamegraphPreferences} from 'sentry/utils/profiling/flamegraph/hooks/useFlamegraphPreferences';
  6. import {useFlamegraphTheme} from 'sentry/utils/profiling/flamegraph/useFlamegraphTheme';
  7. import {
  8. useResizableDrawer,
  9. UseResizableDrawerOptions,
  10. } from 'sentry/utils/profiling/hooks/useResizableDrawer';
  11. // 664px is approximately the width where we start to scroll inside
  12. // 30px is the min height to where the drawer can still be resized
  13. const MIN_FRAMESTACK_DIMENSIONS: [number, number] = [680, 30];
  14. interface ProfilingFlamechartLayoutProps {
  15. flamechart: React.ReactElement;
  16. frameStack: React.ReactElement;
  17. minimap: React.ReactElement;
  18. }
  19. export function ProfilingFlamechartLayout(props: ProfilingFlamechartLayoutProps) {
  20. const flamegraphTheme = useFlamegraphTheme();
  21. const {layout} = useFlamegraphPreferences();
  22. const frameStackRef = useRef<HTMLDivElement>(null);
  23. const resizableOptions: UseResizableDrawerOptions = useMemo(() => {
  24. const initialDimensions: [number, number] = [
  25. // Half the screen minus the ~sidebar width
  26. Math.max(window.innerWidth * 0.5 - 220, MIN_FRAMESTACK_DIMENSIONS[0]),
  27. (flamegraphTheme.SIZES.FLAMEGRAPH_DEPTH_OFFSET + 2) *
  28. flamegraphTheme.SIZES.BAR_HEIGHT,
  29. ];
  30. const onResize = (
  31. newDimensions: [number, number],
  32. maybeOldDimensions: [number, number] | undefined
  33. ) => {
  34. if (!frameStackRef.current) {
  35. return;
  36. }
  37. if (layout === 'table left' || layout === 'table right') {
  38. frameStackRef.current.style.width = `${
  39. maybeOldDimensions?.[0] ?? newDimensions[0]
  40. }px`;
  41. frameStackRef.current.style.height = `100%`;
  42. } else {
  43. frameStackRef.current.style.height = `${
  44. maybeOldDimensions?.[1] ?? newDimensions[1]
  45. }px`;
  46. frameStackRef.current.style.width = `100%`;
  47. }
  48. };
  49. return {
  50. initialDimensions,
  51. onResize,
  52. direction:
  53. layout === 'table left'
  54. ? 'horizontal-ltr'
  55. : layout === 'table right'
  56. ? 'horizontal-rtl'
  57. : 'vertical',
  58. min: MIN_FRAMESTACK_DIMENSIONS,
  59. };
  60. }, [
  61. flamegraphTheme.SIZES.FLAMEGRAPH_DEPTH_OFFSET,
  62. flamegraphTheme.SIZES.BAR_HEIGHT,
  63. layout,
  64. ]);
  65. const {onMouseDown} = useResizableDrawer(resizableOptions);
  66. return (
  67. <ProfilingFlamechartLayoutContainer>
  68. <ProfilingFlamechartGrid layout={layout}>
  69. <MinimapContainer height={flamegraphTheme.SIZES.MINIMAP_HEIGHT}>
  70. {props.minimap}
  71. </MinimapContainer>
  72. <ZoomViewContainer>{props.flamechart}</ZoomViewContainer>
  73. <FrameStackContainer ref={frameStackRef} layout={layout}>
  74. {cloneElement(props.frameStack, {onResize: onMouseDown})}
  75. </FrameStackContainer>
  76. </ProfilingFlamechartGrid>
  77. </ProfilingFlamechartLayoutContainer>
  78. );
  79. }
  80. const ProfilingFlamechartLayoutContainer = styled('div')`
  81. display: flex;
  82. flex: 1 1 100%;
  83. `;
  84. const ProfilingFlamechartGrid = styled('div')<{
  85. layout?: FlamegraphPreferences['layout'];
  86. }>`
  87. display: grid;
  88. width: 100%;
  89. grid-template-rows: ${({layout}) =>
  90. layout === 'table bottom'
  91. ? 'auto 1fr'
  92. : layout === 'table right'
  93. ? '100px auto'
  94. : '100px auto'};
  95. grid-template-columns: ${({layout}) =>
  96. layout === 'table bottom'
  97. ? '100%'
  98. : layout === 'table left'
  99. ? `min-content auto`
  100. : `auto min-content`};
  101. /* false positive for grid layout */
  102. /* stylelint-disable */
  103. grid-template-areas: ${({layout}) =>
  104. layout === 'table bottom'
  105. ? `
  106. 'minimap'
  107. 'flamegraph'
  108. 'frame-stack'
  109. `
  110. : layout === 'table right'
  111. ? `
  112. 'minimap frame-stack'
  113. 'flamegraph frame-stack'
  114. `
  115. : layout === 'table left'
  116. ? `
  117. 'frame-stack minimap'
  118. 'frame-stack flamegraph'
  119. `
  120. : ''};
  121. `;
  122. const MinimapContainer = styled('div')<{
  123. height: FlamegraphTheme['SIZES']['MINIMAP_HEIGHT'];
  124. }>`
  125. position: relative;
  126. height: ${p => p.height}px;
  127. grid-area: minimap;
  128. `;
  129. const ZoomViewContainer = styled('div')`
  130. display: flex;
  131. flex-direction: column;
  132. flex: 1 1 100%;
  133. grid-area: flamegraph;
  134. position: relative;
  135. `;
  136. const FrameStackContainer = styled('div')<{layout: FlamegraphPreferences['layout']}>`
  137. grid-area: frame-stack;
  138. position: relative;
  139. overflow: hidden;
  140. min-width: ${MIN_FRAMESTACK_DIMENSIONS[0]}px;
  141. > div {
  142. position: absolute;
  143. left: 0;
  144. top: 0;
  145. width: 100%;
  146. height: 100%;
  147. }
  148. `;