fix album group height calculation on index reorder

This commit is contained in:
jeffvli
2026-07-11 19:20:22 -07:00
parent 13c9602ffb
commit 2e6e7839c1
5 changed files with 63 additions and 22 deletions
@@ -31,21 +31,21 @@ import { LibraryItem, Song } from '/@/shared/types/domain-types';
import { Play } from '/@/shared/types/types';
interface AlbumGroupHeaderProps {
groupKey?: string;
groupRowCount?: number;
metadata: AlbumGroupMetadata;
onPlay?: (playType: Play) => void;
rowIndex?: number;
setAlbumGroupContentHeight?: (rowIndex: number, height: number) => void;
setAlbumGroupContentHeight?: (groupKey: string, height: number) => void;
size?: AlbumGroupTextSize;
song: Song | undefined;
storedContentHeight?: number;
}
export const AlbumGroupHeader = ({
groupKey,
groupRowCount,
metadata,
onPlay,
rowIndex,
setAlbumGroupContentHeight,
size = 'normal',
song,
@@ -55,7 +55,9 @@ export const AlbumGroupHeader = ({
const albumGroupItems = useAlbumGroupItems();
const showFavoriteRating = useAlbumGroupShowFavoriteRating();
const [isHovered, setIsHovered] = useState(false);
const [resolvedInfoHeight, setResolvedInfoHeight] = useState<number | undefined>();
const [resolved, setResolved] = useState<null | { forInfoHeight: number; height: number }>(
null,
);
const playButtonBehavior = usePlayButtonBehavior();
const albumImageSize = useAlbumGroupImageSize();
const rowHeight = {
@@ -87,6 +89,13 @@ export const AlbumGroupHeader = ({
: groupRowCount * rowHeight
: undefined;
// Ignore resolved height from a previous (larger) group span so minHeight
// cannot keep scrollHeight measurement stuck after a split/shrink.
const resolvedInfoHeight =
resolved && infoHeight !== undefined && resolved.forInfoHeight === infoHeight
? resolved.height
: undefined;
const imageContainerStyle =
albumImageSize > 0
? {
@@ -108,12 +117,14 @@ export const AlbumGroupHeader = ({
const measure = () => {
const contentHeight = infoEl.scrollHeight;
const resolved = Math.max(infoHeight ?? 0, contentHeight);
const resolvedHeight = Math.max(infoHeight ?? 0, contentHeight);
setResolvedInfoHeight(resolved);
if (infoHeight !== undefined) {
setResolved({ forInfoHeight: infoHeight, height: resolvedHeight });
}
if (rowIndex !== undefined && setAlbumGroupContentHeight) {
setAlbumGroupContentHeight(rowIndex, contentHeight);
if (groupKey !== undefined && setAlbumGroupContentHeight) {
setAlbumGroupContentHeight(groupKey, contentHeight);
}
};
@@ -124,9 +135,10 @@ export const AlbumGroupHeader = ({
return () => resizeObserver.disconnect();
}, [
groupKey,
groupRowCount,
infoHeight,
metadataRows.length,
rowIndex,
setAlbumGroupContentHeight,
storedContentHeight,
]);
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { AlbumGroupHeader } from '/@/renderer/components/item-list/item-table-list/album-group-header';
import { computeAlbumGroupMetadata } from '/@/renderer/components/item-list/item-table-list/album-group-metadata';
import {
getAlbumGroupHeightKey,
isLastInAlbumGroup,
ItemTableListInnerColumn,
TableColumnContainer,
@@ -89,6 +90,10 @@ export const AlbumGroupColumn = (props: ItemTableListInnerColumn) => {
}
const metadata = computeAlbumGroupMetadata(groupSongs, groupRowCount, t);
const groupHeightKey = getAlbumGroupHeightKey(item, groupRowCount);
const storedContentHeight = groupHeightKey
? props.albumGroupContentHeights?.get(groupHeightKey)
: undefined;
return (
<TableColumnContainer
@@ -98,14 +103,14 @@ export const AlbumGroupColumn = (props: ItemTableListInnerColumn) => {
isDraggedOver={null}
>
<AlbumGroupHeader
groupKey={groupHeightKey}
groupRowCount={groupRowCount}
metadata={metadata}
onPlay={handlePlay}
rowIndex={props.rowIndex}
setAlbumGroupContentHeight={props.setAlbumGroupContentHeight}
size={props.size === 'default' ? 'normal' : props.size}
song={item}
storedContentHeight={props.albumGroupContentHeights?.get(props.rowIndex)}
storedContentHeight={storedContentHeight}
/>
</TableColumnContainer>
);
@@ -19,7 +19,7 @@ export const useTableScrollToIndex = ({
rowRef,
scrollCellProps,
}: {
albumGroupContentHeights: Map<number, number>;
albumGroupContentHeights: Map<string, number>;
autoScrollToActiveRow: boolean;
enableHeader: boolean;
getRowHeight: (index: number, cellProps: TableItemProps) => number;
@@ -384,6 +384,22 @@ export const ItemTableListColumn = memo(ItemTableListColumnBase, (prevProps, nex
const NonMutedColumns = [TableColumn.TITLE, TableColumn.TITLE_ARTIST, TableColumn.TITLE_COMBINED];
/** Stable key for album-group content heights (survives row moves; not row index). */
export function getAlbumGroupHeightKey(item: unknown, groupRowCount?: number): string | undefined {
if (!item || typeof item !== 'object') return undefined;
let itemKey: string | undefined;
if ('_uniqueId' in item && typeof (item as { _uniqueId?: unknown })._uniqueId === 'string') {
itemKey = (item as { _uniqueId: string })._uniqueId;
} else if ('id' in item && typeof (item as { id?: unknown }).id === 'string') {
itemKey = (item as { id: string }).id;
}
if (!itemKey) return undefined;
if (groupRowCount === undefined) return itemKey;
return `${itemKey}:${groupRowCount}`;
}
// Counts how many consecutive rows belong to the same album group as `rowIndex`.
export function getAlbumGroupRowCount(
rowIndex: number,
@@ -550,7 +566,10 @@ function getAlbumGroupClampHeight(props: ItemTableListInnerColumn): null | numbe
props.getRowItem,
props.enableHeader,
);
const contentHeight = props.albumGroupContentHeights?.get(groupStartRowIndex) ?? 0;
const groupStartItem = props.getRowItem?.(groupStartRowIndex);
const groupHeightKey = getAlbumGroupHeightKey(groupStartItem, groupRowCount);
const contentHeight =
(groupHeightKey ? props.albumGroupContentHeights?.get(groupHeightKey) : undefined) ?? 0;
const totalGroupHeight = getAlbumGroupSpanHeight(
groupRowCount,
baseHeight,
@@ -45,6 +45,7 @@ import { useTablePaneSync } from '/@/renderer/components/item-list/item-table-li
import { useTableRowModel } from '/@/renderer/components/item-list/item-table-list/hooks/use-table-row-model';
import { useTableScrollToIndex } from '/@/renderer/components/item-list/item-table-list/hooks/use-table-scroll-to-index';
import {
getAlbumGroupHeightKey,
getAlbumGroupRowCount,
getAlbumGroupSpanHeight,
getAlbumGroupStartRowIndex,
@@ -176,7 +177,7 @@ const ItemTableScrollShadowRight = memo(function ItemTableScrollShadowRight({
ItemTableScrollShadowRight.displayName = 'ItemTableScrollShadowRight';
interface VirtualizedTableGridProps {
albumGroupContentHeights: Map<number, number>;
albumGroupContentHeights: Map<string, number>;
calculatedColumnWidths: number[];
CellComponent: JSXElementConstructor<CellComponentProps<TableItemProps>>;
data: unknown[];
@@ -194,7 +195,7 @@ interface VirtualizedTableGridProps {
pinnedRowCount: number;
pinnedRowRef: React.RefObject<HTMLDivElement | null>;
scrollShadowStore: TableScrollShadowStore;
setAlbumGroupContentHeight: (rowIndex: number, height: number) => void;
setAlbumGroupContentHeight: (groupKey: string, height: number) => void;
tableConfig: ItemTableListConfig;
totalColumnCount: number;
totalRowCount: number;
@@ -773,7 +774,7 @@ export interface TableGroupHeader {
export interface TableItemProps {
adjustedRowIndexMap?: Map<number, number>;
albumGroupContentHeights?: Map<number, number>;
albumGroupContentHeights?: Map<string, number>;
albumGroupImageSize?: number;
calculatedColumnWidths?: number[];
cellPadding?: ItemTableListProps['cellPadding'];
@@ -807,7 +808,7 @@ export interface TableItemProps {
pinnedRightColumnWidths?: number[];
playerContext: PlayerContext;
playlistId?: string;
setAlbumGroupContentHeight?: (rowIndex: number, height: number) => void;
setAlbumGroupContentHeight?: (groupKey: string, height: number) => void;
size?: ItemTableListProps['size'];
startRowIndex?: number;
tableId: string;
@@ -1298,14 +1299,14 @@ const BaseItemTableList = ({
const albumGroupImageSize = useAlbumGroupImageSize();
const baseItemCount = itemCount ?? data.length;
const [albumGroupContentHeights, setAlbumGroupContentHeights] = useState(
() => new Map<number, number>(),
() => new Map<string, number>(),
);
const setAlbumGroupContentHeight = useCallback((rowIndex: number, height: number) => {
const setAlbumGroupContentHeight = useCallback((groupKey: string, height: number) => {
setAlbumGroupContentHeights((prev) => {
if (prev.get(rowIndex) === height) return prev;
if (prev.get(groupKey) === height) return prev;
const next = new Map(prev);
next.set(rowIndex, height);
next.set(groupKey, height);
return next;
});
}, []);
@@ -1460,8 +1461,12 @@ const BaseItemTableList = ({
cellProps.getRowItem,
cellProps.enableHeader,
);
const groupStartItem = cellProps.getRowItem?.(groupStartRowIndex);
const groupHeightKey = getAlbumGroupHeightKey(groupStartItem, groupRowCount);
const contentHeight =
cellProps.albumGroupContentHeights?.get(groupStartRowIndex) ?? 0;
(groupHeightKey
? cellProps.albumGroupContentHeights?.get(groupHeightKey)
: undefined) ?? 0;
const totalGroupHeight = getAlbumGroupSpanHeight(
groupRowCount,
baseHeight,