index.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import {useRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import ErrorBoundary from 'sentry/components/errorBoundary';
  4. import ReplayTimeline from 'sentry/components/replays/breadcrumbs/replayTimeline';
  5. import ReplayController from 'sentry/components/replays/replayController';
  6. import ReplayView from 'sentry/components/replays/replayView';
  7. import {space} from 'sentry/styles/space';
  8. import {LayoutKey} from 'sentry/utils/replays/hooks/useReplayLayout';
  9. import {useDimensions} from 'sentry/utils/useDimensions';
  10. import useOrganization from 'sentry/utils/useOrganization';
  11. import useFullscreen from 'sentry/utils/window/useFullscreen';
  12. import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight';
  13. import FluidPanel from 'sentry/views/replays/detail/layout/fluidPanel';
  14. import FocusArea from 'sentry/views/replays/detail/layout/focusArea';
  15. import FocusTabs from 'sentry/views/replays/detail/layout/focusTabs';
  16. import SplitPanel from 'sentry/views/replays/detail/layout/splitPanel';
  17. const MIN_CONTENT_WIDTH = 340;
  18. const MIN_SIDEBAR_WIDTH = 325;
  19. const MIN_VIDEO_HEIGHT = 200;
  20. const MIN_CONTENT_HEIGHT = 180;
  21. const DIVIDER_SIZE = 16;
  22. type Props = {
  23. layout?: LayoutKey;
  24. };
  25. function ReplayLayout({layout = LayoutKey.TOPBAR}: Props) {
  26. const fullscreenRef = useRef(null);
  27. const {toggle: toggleFullscreen} = useFullscreen({
  28. elementRef: fullscreenRef,
  29. });
  30. const measureRef = useRef<HTMLDivElement>(null);
  31. const {width, height} = useDimensions({elementRef: measureRef});
  32. const organization = useOrganization();
  33. const hasNewTimeline = organization.features.includes('session-replay-new-timeline');
  34. const timeline = hasNewTimeline ? null : (
  35. <ErrorBoundary mini>
  36. <ReplayTimeline />
  37. </ErrorBoundary>
  38. );
  39. const video = (
  40. <VideoSection ref={fullscreenRef}>
  41. <ErrorBoundary mini>
  42. <ReplayView toggleFullscreen={toggleFullscreen} />
  43. </ErrorBoundary>
  44. </VideoSection>
  45. );
  46. const controller = hasNewTimeline ? (
  47. <ErrorBoundary>
  48. <ReplayController toggleFullscreen={toggleFullscreen} />
  49. </ErrorBoundary>
  50. ) : null;
  51. if (layout === LayoutKey.VIDEO_ONLY) {
  52. return (
  53. <BodyContent>
  54. {timeline}
  55. {video}
  56. {controller}
  57. </BodyContent>
  58. );
  59. }
  60. const focusArea = (
  61. <ErrorBoundary mini>
  62. <FluidPanel title={<SmallMarginFocusTabs />}>
  63. <FocusArea />
  64. </FluidPanel>
  65. </ErrorBoundary>
  66. );
  67. const hasSize = width + height > 0;
  68. if (layout === LayoutKey.NO_VIDEO) {
  69. return (
  70. <BodyContent style={{gridTemplateRows: hasNewTimeline ? '1fr auto' : 'auto 1fr'}}>
  71. {timeline}
  72. <FluidHeight ref={measureRef}>
  73. {hasSize ? <PanelContainer key={layout}>{focusArea}</PanelContainer> : null}
  74. </FluidHeight>
  75. </BodyContent>
  76. );
  77. }
  78. if (layout === LayoutKey.SIDEBAR_LEFT) {
  79. return (
  80. <BodyContent style={{gridTemplateRows: hasNewTimeline ? '1fr auto' : 'auto 1fr'}}>
  81. {timeline}
  82. <FluidHeight ref={measureRef}>
  83. {hasSize ? (
  84. <SplitPanel
  85. key={layout}
  86. availableSize={width}
  87. left={{
  88. content: <PanelContainer key={layout}>{video}</PanelContainer>,
  89. default: width * 0.5,
  90. min: MIN_SIDEBAR_WIDTH,
  91. max: width - MIN_CONTENT_WIDTH,
  92. }}
  93. right={focusArea}
  94. />
  95. ) : null}
  96. </FluidHeight>
  97. {controller}
  98. </BodyContent>
  99. );
  100. }
  101. // layout === 'topbar'
  102. return (
  103. <BodyContent style={{gridTemplateRows: hasNewTimeline ? '1fr auto' : 'auto 1fr'}}>
  104. {timeline}
  105. <FluidHeight ref={measureRef}>
  106. {hasSize ? (
  107. <SplitPanel
  108. key={layout}
  109. availableSize={height}
  110. top={{
  111. content: <PanelContainer>{video}</PanelContainer>,
  112. default: (height - DIVIDER_SIZE) * 0.5,
  113. min: MIN_VIDEO_HEIGHT,
  114. max: height - DIVIDER_SIZE - MIN_CONTENT_HEIGHT,
  115. }}
  116. bottom={focusArea}
  117. />
  118. ) : null}
  119. </FluidHeight>
  120. {controller}
  121. </BodyContent>
  122. );
  123. }
  124. const BodyContent = styled('main')`
  125. background: ${p => p.theme.background};
  126. width: 100%;
  127. height: 100%;
  128. display: grid;
  129. grid-template-rows: auto 1fr;
  130. gap: ${space(2)};
  131. overflow: hidden;
  132. padding: ${space(2)};
  133. `;
  134. const SmallMarginFocusTabs = styled(FocusTabs)`
  135. margin-bottom: ${space(1)};
  136. `;
  137. const VideoSection = styled(FluidHeight)`
  138. background: ${p => p.theme.background};
  139. gap: ${space(1)};
  140. :fullscreen {
  141. padding: ${space(1)};
  142. }
  143. `;
  144. const PanelContainer = styled('div')`
  145. width: 100%;
  146. height: 100%;
  147. position: relative;
  148. display: grid;
  149. overflow: auto;
  150. &.disable-iframe-pointer iframe {
  151. pointer-events: none !important;
  152. }
  153. `;
  154. export default ReplayLayout;