index.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  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 ReplayView from 'sentry/components/replays/replayView';
  6. import {space} from 'sentry/styles/space';
  7. import {LayoutKey} from 'sentry/utils/replays/hooks/useReplayLayout';
  8. import {useDimensions} from 'sentry/utils/useDimensions';
  9. import useFullscreen from 'sentry/utils/window/useFullscreen';
  10. import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight';
  11. import FluidPanel from 'sentry/views/replays/detail/layout/fluidPanel';
  12. import FocusArea from 'sentry/views/replays/detail/layout/focusArea';
  13. import FocusTabs from 'sentry/views/replays/detail/layout/focusTabs';
  14. import SidebarArea from 'sentry/views/replays/detail/layout/sidebarArea';
  15. import SideTabs from 'sentry/views/replays/detail/layout/sideTabs';
  16. import SplitPanel from 'sentry/views/replays/detail/layout/splitPanel';
  17. const MIN_VIDEO_WIDTH = 325;
  18. const MIN_CONTENT_WIDTH = 340;
  19. const MIN_SIDEBAR_WIDTH = 325;
  20. const MIN_VIDEO_HEIGHT = 200;
  21. const MIN_CONTENT_HEIGHT = 180;
  22. const MIN_SIDEBAR_HEIGHT = 120;
  23. const DIVIDER_SIZE = 16;
  24. type Props = {
  25. layout?: LayoutKey;
  26. };
  27. function ReplayLayout({layout = LayoutKey.TOPBAR}: Props) {
  28. const fullscreenRef = useRef(null);
  29. const {toggle: toggleFullscreen} = useFullscreen({
  30. elementRef: fullscreenRef,
  31. });
  32. const measureRef = useRef<HTMLDivElement>(null);
  33. const {width, height} = useDimensions({elementRef: measureRef});
  34. const timeline = (
  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. if (layout === LayoutKey.VIDEO_ONLY) {
  47. return (
  48. <BodyContent>
  49. {timeline}
  50. {video}
  51. </BodyContent>
  52. );
  53. }
  54. const focusArea = (
  55. <ErrorBoundary mini>
  56. <FluidPanel title={<SmallMarginFocusTabs />}>
  57. <FocusArea />
  58. </FluidPanel>
  59. </ErrorBoundary>
  60. );
  61. const sidebarArea = (
  62. <ErrorBoundary mini>
  63. <FluidPanel title={<SmallMarginSideTabs />}>
  64. <SidebarArea />
  65. </FluidPanel>
  66. </ErrorBoundary>
  67. );
  68. const hasSize = width + height > 0;
  69. if (layout === LayoutKey.NO_VIDEO) {
  70. return (
  71. <BodyContent>
  72. {timeline}
  73. <FluidHeight ref={measureRef}>
  74. {hasSize ? (
  75. <SplitPanel
  76. key={layout}
  77. availableSize={width}
  78. left={{
  79. content: focusArea,
  80. default: (width - DIVIDER_SIZE) * 0.9,
  81. min: 0,
  82. max: width - DIVIDER_SIZE,
  83. }}
  84. right={sidebarArea}
  85. />
  86. ) : null}
  87. </FluidHeight>
  88. </BodyContent>
  89. );
  90. }
  91. if (layout === LayoutKey.SIDEBAR_LEFT) {
  92. return (
  93. <BodyContent>
  94. {timeline}
  95. <FluidHeight ref={measureRef}>
  96. {hasSize ? (
  97. <SplitPanel
  98. key={layout}
  99. availableSize={width}
  100. left={{
  101. content: (
  102. <SplitPanel
  103. key={layout}
  104. availableSize={height}
  105. top={{
  106. content: video,
  107. default: (height - DIVIDER_SIZE) * 0.65,
  108. min: MIN_CONTENT_HEIGHT,
  109. max: height - DIVIDER_SIZE - MIN_SIDEBAR_HEIGHT,
  110. }}
  111. bottom={sidebarArea}
  112. />
  113. ),
  114. default: (width - DIVIDER_SIZE) * 0.5,
  115. min: MIN_SIDEBAR_WIDTH,
  116. max: width - DIVIDER_SIZE - MIN_CONTENT_WIDTH,
  117. }}
  118. right={focusArea}
  119. />
  120. ) : null}
  121. </FluidHeight>
  122. </BodyContent>
  123. );
  124. }
  125. // layout === 'topbar'
  126. return (
  127. <BodyContent>
  128. {timeline}
  129. <FluidHeight ref={measureRef}>
  130. {hasSize ? (
  131. <SplitPanel
  132. key={layout}
  133. availableSize={height}
  134. top={{
  135. content: (
  136. <SplitPanel
  137. availableSize={width}
  138. left={{
  139. content: video,
  140. default: (width - DIVIDER_SIZE) * 0.5,
  141. min: MIN_VIDEO_WIDTH,
  142. max: width - DIVIDER_SIZE - MIN_SIDEBAR_WIDTH,
  143. }}
  144. right={sidebarArea}
  145. />
  146. ),
  147. default: (height - DIVIDER_SIZE) * 0.5,
  148. min: MIN_VIDEO_HEIGHT,
  149. max: height - DIVIDER_SIZE - MIN_CONTENT_HEIGHT,
  150. }}
  151. bottom={focusArea}
  152. />
  153. ) : null}
  154. </FluidHeight>
  155. </BodyContent>
  156. );
  157. }
  158. const BodyContent = styled('main')`
  159. background: ${p => p.theme.background};
  160. width: 100%;
  161. height: 100%;
  162. display: grid;
  163. grid-template-rows: auto 1fr;
  164. gap: ${space(2)};
  165. overflow: hidden;
  166. padding: ${space(2)};
  167. `;
  168. const SmallMarginFocusTabs = styled(FocusTabs)`
  169. margin-bottom: ${space(1)};
  170. `;
  171. const SmallMarginSideTabs = styled(SideTabs)`
  172. margin-bottom: ${space(1)};
  173. `;
  174. const VideoSection = styled(FluidHeight)`
  175. background: ${p => p.theme.background};
  176. gap: ${space(1)};
  177. :fullscreen {
  178. padding: ${space(1)};
  179. }
  180. `;
  181. export default ReplayLayout;