index.tsx 4.6 KB

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