diff --git a/static/app/views/preprod/snapshots/main/snapshotListView.tsx b/static/app/views/preprod/snapshots/main/snapshotListView.tsx
index c3f9cf6413e5b9..8ec13102f0948a 100644
--- a/static/app/views/preprod/snapshots/main/snapshotListView.tsx
+++ b/static/app/views/preprod/snapshots/main/snapshotListView.tsx
@@ -3,6 +3,7 @@ import {
useCallback,
useEffect,
useImperativeHandle,
+ useLayoutEffect,
useMemo,
useRef,
useState,
@@ -86,17 +87,16 @@ const CARD_CHROME_HEIGHT = 120;
const CARD_GAP = 0;
const GROUP_PADDING = 0;
const ROW_PADDING_BOTTOM = 16;
-const LIST_CONTENT_WIDTH_ASSUMPTION = 900;
+const DEFAULT_CONTENT_WIDTH = 900;
const SNAPSHOT_FRAME_BORDER_WIDTH = 1;
const STICKY_HEADER_BOTTOM_OVERLAP = SNAPSHOT_FRAME_BORDER_WIDTH * 2;
-function estimateCardHeight(image: SnapshotImage, splitColumns: boolean) {
- const columnWidth = splitColumns
- ? LIST_CONTENT_WIDTH_ASSUMPTION / 2
- : LIST_CONTENT_WIDTH_ASSUMPTION;
- // The
uses width: auto + max-width: 100%, so it never scales up past
- // its natural size. Mirror that here: only scale down when natural width
- // exceeds the column.
+function estimateCardHeight(
+ image: SnapshotImage,
+ splitColumns: boolean,
+ contentWidth: number
+) {
+ const columnWidth = splitColumns ? contentWidth / 2 : contentWidth;
const aspectHeight =
image.width > 0 && image.height > 0
? image.width <= columnWidth
@@ -114,7 +114,7 @@ export function isItemUngrouped(item: SidebarItem): boolean {
return !item.images[0]?.group;
}
-function buildGroups(items: SidebarItem[]): GroupRow[] {
+function buildGroups(items: SidebarItem[], contentWidth: number): GroupRow[] {
const groups: GroupRow[] = [];
for (const item of items) {
const cards: GroupCard[] = [];
@@ -122,11 +122,11 @@ function buildGroups(items: SidebarItem[]): GroupRow[] {
for (const pair of item.pairs) {
cards.push({
type: 'pair-card',
- id: `c:${item.key}:${pair.head_image.key}`,
+ id: `c:${item.key}:${pair.head_image.image_file_name}`,
pair,
estimatedHeight: Math.max(
- estimateCardHeight(pair.head_image, true),
- estimateCardHeight(pair.base_image, true)
+ estimateCardHeight(pair.head_image, true, contentWidth),
+ estimateCardHeight(pair.base_image, true, contentWidth)
),
});
}
@@ -134,21 +134,21 @@ function buildGroups(items: SidebarItem[]): GroupRow[] {
for (const pair of item.pairs) {
cards.push({
type: 'image-card',
- id: `c:${item.key}:${pair.head_image.key}`,
+ id: `c:${item.key}:${pair.head_image.image_file_name}`,
image: pair.head_image,
copyData: pair,
cardType: item.type,
- estimatedHeight: estimateCardHeight(pair.head_image, false),
+ estimatedHeight: estimateCardHeight(pair.head_image, false, contentWidth),
});
}
} else {
for (const image of item.images) {
cards.push({
type: 'image-card',
- id: `c:${item.key}:${image.key}`,
+ id: `c:${item.key}:${image.image_file_name}`,
image,
cardType: item.type,
- estimatedHeight: estimateCardHeight(image, false),
+ estimatedHeight: estimateCardHeight(image, false, contentWidth),
});
}
}
@@ -192,10 +192,40 @@ export const SnapshotListView = memo(function SnapshotListView({
onVisibleGroupChange,
}: SnapshotListViewProps) {
const theme = useTheme();
- const groups = useMemo(() => buildGroups(items), [items]);
const scrollRef = useRef(null);
const [scrollTop, setScrollTop] = useState(0);
+ const [contentWidth, setContentWidth] = useState(DEFAULT_CONTENT_WIDTH);
+
+ useLayoutEffect(() => {
+ const el = scrollRef.current;
+ if (!el) {
+ return;
+ }
+ const style = getComputedStyle(el);
+ setContentWidth(
+ el.clientWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight)
+ );
+ }, []);
+
+ useEffect(() => {
+ const el = scrollRef.current;
+ if (!el) {
+ return;
+ }
+ const observer = new ResizeObserver(entries => {
+ for (const entry of entries) {
+ if (entry.contentRect.width > 0) {
+ setContentWidth(entry.contentRect.width);
+ }
+ }
+ });
+ observer.observe(el);
+ return () => observer.disconnect();
+ }, []);
+
+ const groups = useMemo(() => buildGroups(items, contentWidth), [items, contentWidth]);
+
const virtualizer = useVirtualizer({
count: groups.length,
getScrollElement: () => scrollRef.current,
@@ -204,7 +234,6 @@ export const SnapshotListView = memo(function SnapshotListView({
overscan: 5,
scrollPaddingEnd: 8,
});
- virtualizer.shouldAdjustScrollPositionOnItemSizeChange = () => false;
// Flat (snapshotKey -> groupIdx / position) index for keyboard nav and scroll; O(1) lookups
const flatIndex = useMemo(() => {
@@ -339,22 +368,27 @@ export const SnapshotListView = memo(function SnapshotListView({
}
didInitialScroll.current = true;
virtualizer.scrollToIndex(targetIdx, {align: 'start'});
- requestAnimationFrame(() => {
+
+ const isUngrouped =
+ groups[flatIndex.groupIdxByKey.get(initialSnapshotKey) ?? -1]?.isUngrouped ?? true;
+
+ let retries = 3;
+ const adjustScroll = () => {
const el = scrollRef.current?.querySelector(
`[data-snapshot-key="${CSS.escape(initialSnapshotKey)}"]`
);
- if (el) {
- el.scrollIntoView({block: 'start'});
- const groupIdx = flatIndex.groupIdxByKey.get(initialSnapshotKey);
- if (
- groupIdx !== undefined &&
- !groups[groupIdx]?.isUngrouped &&
- scrollRef.current
- ) {
- scrollRef.current.scrollTop -= SNAPSHOT_GROUP_HEADER_HEIGHT;
+ if (!el || !scrollRef.current) {
+ if (retries-- > 0) {
+ requestAnimationFrame(adjustScroll);
}
+ return;
}
- });
+ el.scrollIntoView({block: 'start'});
+ if (!isUngrouped) {
+ scrollRef.current.scrollTop -= SNAPSHOT_GROUP_HEADER_HEIGHT;
+ }
+ };
+ requestAnimationFrame(adjustScroll);
}, [groups, initialSnapshotKey, flatIndex, virtualizer]);
const keyNavRef = useRef({