Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 63 additions & 29 deletions static/app/views/preprod/snapshots/main/snapshotListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
useCallback,
useEffect,
useImperativeHandle,
useLayoutEffect,
useMemo,
useRef,
useState,
Expand Down Expand Up @@ -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 <img> 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
Expand All @@ -114,41 +114,41 @@ 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[] = [];
if (item.type === 'changed') {
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)
),
});
}
} else if (item.type === 'renamed') {
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),
});
}
}
Expand Down Expand Up @@ -192,10 +192,40 @@ export const SnapshotListView = memo(function SnapshotListView({
onVisibleGroupChange,
}: SnapshotListViewProps) {
const theme = useTheme();
const groups = useMemo(() => buildGroups(items), [items]);
const scrollRef = useRef<HTMLDivElement>(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,
Expand All @@ -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(() => {
Expand Down Expand Up @@ -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<HTMLElement>(
`[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({
Expand Down
Loading