mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-14 14:40:08 +02:00
add additional height calculation for album group
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
display: flex;
|
||||
gap: var(--theme-spacing-xs);
|
||||
align-items: center;
|
||||
margin-top: var(--theme-spacing-xxs);
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.container {
|
||||
display: flex;
|
||||
gap: var(--theme-spacing-sm);
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 var(--theme-spacing-xs);
|
||||
@@ -42,15 +43,23 @@
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: grid;
|
||||
grid-template-rows: auto auto auto;
|
||||
gap: 0;
|
||||
align-content: start;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
padding-top: calc(var(--theme-spacing-xs) * 0.5);
|
||||
}
|
||||
|
||||
.info.enlarged-image {
|
||||
padding-top: var(--theme-spacing-xs);
|
||||
}
|
||||
|
||||
.album-name {
|
||||
font-size: var(--theme-font-size-sm);
|
||||
line-height: 1.25;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.album-name a {
|
||||
@@ -64,5 +73,13 @@
|
||||
|
||||
.artist-name {
|
||||
font-size: var(--theme-font-size-xs);
|
||||
line-height: 1.25;
|
||||
overflow-wrap: anywhere;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.controls-row {
|
||||
flex-shrink: 0;
|
||||
min-height: 22px;
|
||||
margin-top: var(--theme-spacing-xxs);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ReactElement, useState } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { ReactElement, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { generatePath, Link } from 'react-router';
|
||||
|
||||
import imageColumnStyles from '../item-detail-list/columns/image-column.module.css';
|
||||
@@ -21,6 +22,8 @@ import { Play } from '/@/shared/types/types';
|
||||
interface AlbumGroupHeaderProps {
|
||||
groupRowCount?: number;
|
||||
onPlay?: (playType: Play) => void;
|
||||
rowIndex?: number;
|
||||
setAlbumGroupContentHeight?: (rowIndex: number, height: number) => void;
|
||||
size?: 'compact' | 'large' | 'normal';
|
||||
song: Song | undefined;
|
||||
}
|
||||
@@ -28,10 +31,13 @@ interface AlbumGroupHeaderProps {
|
||||
export const AlbumGroupHeader = ({
|
||||
groupRowCount,
|
||||
onPlay,
|
||||
rowIndex,
|
||||
setAlbumGroupContentHeight,
|
||||
size = 'normal',
|
||||
song,
|
||||
}: AlbumGroupHeaderProps): ReactElement => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [resolvedInfoHeight, setResolvedInfoHeight] = useState<number | undefined>();
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
const albumImageSize = useAlbumGroupImageSize();
|
||||
const rowHeight = {
|
||||
@@ -66,6 +72,31 @@ export const AlbumGroupHeader = ({
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const infoRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const infoEl = infoRef.current;
|
||||
if (!infoEl) return;
|
||||
|
||||
const measure = () => {
|
||||
const contentHeight = infoEl.scrollHeight;
|
||||
const resolved = Math.max(infoHeight ?? 0, contentHeight);
|
||||
|
||||
setResolvedInfoHeight(resolved);
|
||||
|
||||
if (rowIndex !== undefined && setAlbumGroupContentHeight) {
|
||||
setAlbumGroupContentHeight(rowIndex, contentHeight);
|
||||
}
|
||||
};
|
||||
|
||||
measure();
|
||||
|
||||
const resizeObserver = new ResizeObserver(measure);
|
||||
resizeObserver.observe(infoEl);
|
||||
|
||||
return () => resizeObserver.disconnect();
|
||||
}, [infoHeight, rowIndex, setAlbumGroupContentHeight]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.container}
|
||||
@@ -100,7 +131,11 @@ export const AlbumGroupHeader = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.info} style={{ height: infoHeight }}>
|
||||
<div
|
||||
className={clsx(styles.info, albumImageSize > 0 && styles.enlargedImage)}
|
||||
ref={infoRef}
|
||||
style={{ minHeight: resolvedInfoHeight ?? infoHeight }}
|
||||
>
|
||||
<div className={styles.albumName}>
|
||||
{song?.albumId && albumPath ? (
|
||||
<Link state={{ item: song }} to={albumPath}>
|
||||
@@ -118,12 +153,14 @@ export const AlbumGroupHeader = ({
|
||||
rootTextProps={{ fw: 400, size: 'xs' }}
|
||||
/>
|
||||
</div>
|
||||
<AlbumGroupControls
|
||||
albumId={song?.albumId}
|
||||
isGroupHovered={isHovered}
|
||||
serverId={song?._serverId}
|
||||
serverType={song?._serverType}
|
||||
/>
|
||||
<div className={styles.controlsRow}>
|
||||
<AlbumGroupControls
|
||||
albumId={song?.albumId}
|
||||
isGroupHovered={isHovered}
|
||||
serverId={song?._serverId}
|
||||
serverType={song?._serverType}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -93,6 +93,8 @@ export const AlbumGroupColumn = (props: ItemTableListInnerColumn) => {
|
||||
<AlbumGroupHeader
|
||||
groupRowCount={groupRowCount}
|
||||
onPlay={handlePlay}
|
||||
rowIndex={props.rowIndex}
|
||||
setAlbumGroupContentHeight={props.setAlbumGroupContentHeight}
|
||||
size={props.size === 'default' ? 'normal' : props.size}
|
||||
song={item}
|
||||
/>
|
||||
|
||||
@@ -414,6 +414,38 @@ export function getAlbumGroupRowCount(
|
||||
return end - start + 1;
|
||||
}
|
||||
|
||||
export function getAlbumGroupSpanHeight(
|
||||
groupRowCount: number,
|
||||
baseHeight: number,
|
||||
albumGroupImageSize: number,
|
||||
contentHeight = 0,
|
||||
): number {
|
||||
const rowSpanHeight = groupRowCount * baseHeight;
|
||||
const imageSpanHeight =
|
||||
albumGroupImageSize > 0 ? Math.max(albumGroupImageSize, rowSpanHeight) : rowSpanHeight;
|
||||
|
||||
return Math.max(imageSpanHeight, contentHeight);
|
||||
}
|
||||
|
||||
export function getAlbumGroupStartRowIndex(
|
||||
rowIndex: number,
|
||||
getRowItem: ((index: number) => unknown) | undefined,
|
||||
enableHeader: boolean | undefined,
|
||||
): number {
|
||||
const item = getRowItem?.(rowIndex) as null | undefined | { album?: string };
|
||||
if (!item?.album) return rowIndex;
|
||||
|
||||
const firstDataRow = enableHeader ? 1 : 0;
|
||||
let start = rowIndex;
|
||||
while (start > firstDataRow) {
|
||||
const prevItem = getRowItem?.(start - 1) as null | undefined | { album?: string };
|
||||
if (!prevItem || prevItem.album !== item.album) break;
|
||||
start--;
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
export function isAlbumGroupingActive(columns: { id: string; isEnabled?: boolean }[]): boolean {
|
||||
return columns.some((col) => col.id === TableColumn.ALBUM_GROUP && col.isEnabled);
|
||||
}
|
||||
@@ -513,9 +545,21 @@ function getAlbumGroupClampHeight(props: ItemTableListInnerColumn): null | numbe
|
||||
props.enableHeader,
|
||||
props.data.length,
|
||||
);
|
||||
const groupStartRowIndex = getAlbumGroupStartRowIndex(
|
||||
props.rowIndex,
|
||||
props.getRowItem,
|
||||
props.enableHeader,
|
||||
);
|
||||
const contentHeight = props.albumGroupContentHeights?.get(groupStartRowIndex) ?? 0;
|
||||
const totalGroupHeight = getAlbumGroupSpanHeight(
|
||||
groupRowCount,
|
||||
baseHeight,
|
||||
albumImageSize,
|
||||
contentHeight,
|
||||
);
|
||||
|
||||
// Only clamp when the row was actually grown to fit the image.
|
||||
if (albumImageSize <= groupRowCount * baseHeight) return null;
|
||||
// Only clamp when the row was actually grown to fit the image or wrapped text.
|
||||
if (totalGroupHeight <= groupRowCount * baseHeight) return null;
|
||||
|
||||
return baseHeight;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ import { useTableRowModel } from '/@/renderer/components/item-list/item-table-li
|
||||
import { useTableScrollToIndex } from '/@/renderer/components/item-list/item-table-list/hooks/use-table-scroll-to-index';
|
||||
import {
|
||||
getAlbumGroupRowCount,
|
||||
getAlbumGroupSpanHeight,
|
||||
getAlbumGroupStartRowIndex,
|
||||
isLastInAlbumGroup,
|
||||
ItemTableListColumn,
|
||||
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||
@@ -174,6 +176,7 @@ const ItemTableScrollShadowRight = memo(function ItemTableScrollShadowRight({
|
||||
ItemTableScrollShadowRight.displayName = 'ItemTableScrollShadowRight';
|
||||
|
||||
interface VirtualizedTableGridProps {
|
||||
albumGroupContentHeights: Map<number, number>;
|
||||
calculatedColumnWidths: number[];
|
||||
CellComponent: JSXElementConstructor<CellComponentProps<TableItemProps>>;
|
||||
data: unknown[];
|
||||
@@ -191,12 +194,14 @@ interface VirtualizedTableGridProps {
|
||||
pinnedRowCount: number;
|
||||
pinnedRowRef: React.RefObject<HTMLDivElement | null>;
|
||||
scrollShadowStore: TableScrollShadowStore;
|
||||
setAlbumGroupContentHeight: (rowIndex: number, height: number) => void;
|
||||
tableConfig: ItemTableListConfig;
|
||||
totalColumnCount: number;
|
||||
totalRowCount: number;
|
||||
}
|
||||
|
||||
const VirtualizedTableGrid = ({
|
||||
albumGroupContentHeights,
|
||||
calculatedColumnWidths,
|
||||
CellComponent,
|
||||
data,
|
||||
@@ -214,6 +219,7 @@ const VirtualizedTableGrid = ({
|
||||
pinnedRowCount,
|
||||
pinnedRowRef,
|
||||
scrollShadowStore,
|
||||
setAlbumGroupContentHeight,
|
||||
tableConfig,
|
||||
totalColumnCount,
|
||||
totalRowCount,
|
||||
@@ -377,41 +383,15 @@ const VirtualizedTableGrid = ({
|
||||
],
|
||||
);
|
||||
|
||||
const gridOnlyProps = useMemo(
|
||||
() => ({
|
||||
calculatedColumnWidths,
|
||||
data: dataWithGroups,
|
||||
getAdjustedRowIndex,
|
||||
getGroupRenderData,
|
||||
getRowItem,
|
||||
groupHeaderInfoByRowIndex,
|
||||
hasAlbumGroupColumn: parsedColumns.some((col) => col.id === TableColumn.ALBUM_GROUP),
|
||||
pinnedLeftColumnCount,
|
||||
pinnedLeftColumnWidths,
|
||||
pinnedRightColumnCount,
|
||||
pinnedRightColumnWidths,
|
||||
}),
|
||||
[
|
||||
calculatedColumnWidths,
|
||||
dataWithGroups,
|
||||
getRowItem,
|
||||
getAdjustedRowIndex,
|
||||
getGroupRenderData,
|
||||
groupHeaderInfoByRowIndex,
|
||||
parsedColumns,
|
||||
pinnedLeftColumnCount,
|
||||
pinnedLeftColumnWidths,
|
||||
pinnedRightColumnCount,
|
||||
pinnedRightColumnWidths,
|
||||
],
|
||||
);
|
||||
|
||||
const itemProps: TableItemProps = useMemo(
|
||||
() => ({
|
||||
albumGroupContentHeights,
|
||||
albumGroupImageSize,
|
||||
calculatedColumnWidths,
|
||||
cellPadding: tableConfig.cellPadding,
|
||||
columns: tableConfig.columns,
|
||||
controls: tableConfig.controls,
|
||||
data: dataWithGroups,
|
||||
enableAlternateRowColors: tableConfig.enableAlternateRowColors,
|
||||
enableColumnReorder: tableConfig.enableColumnReorder,
|
||||
enableColumnResize: tableConfig.enableColumnResize,
|
||||
@@ -422,18 +402,43 @@ const VirtualizedTableGrid = ({
|
||||
enableRowHoverHighlight: tableConfig.enableRowHoverHighlight,
|
||||
enableSelection: tableConfig.enableSelection,
|
||||
enableVerticalBorders: tableConfig.enableVerticalBorders,
|
||||
getAdjustedRowIndex,
|
||||
getGroupRenderData,
|
||||
getRowHeight: tableConfig.getRowHeight,
|
||||
getRowItem,
|
||||
groupHeaderInfoByRowIndex,
|
||||
groups: tableConfig.groups,
|
||||
hasAlbumGroupColumn: parsedColumns.some((col) => col.id === TableColumn.ALBUM_GROUP),
|
||||
internalState: tableConfig.internalState,
|
||||
itemType: tableConfig.itemType,
|
||||
pinnedLeftColumnCount,
|
||||
pinnedLeftColumnWidths,
|
||||
pinnedRightColumnCount,
|
||||
pinnedRightColumnWidths,
|
||||
playerContext: tableConfig.playerContext,
|
||||
playlistId: tableConfig.playlistId,
|
||||
setAlbumGroupContentHeight,
|
||||
size: tableConfig.size,
|
||||
startRowIndex: tableConfig.startRowIndex,
|
||||
tableId: tableConfig.tableId,
|
||||
...gridOnlyProps,
|
||||
}),
|
||||
[albumGroupImageSize, gridOnlyProps, tableConfig],
|
||||
[
|
||||
albumGroupContentHeights,
|
||||
albumGroupImageSize,
|
||||
calculatedColumnWidths,
|
||||
dataWithGroups,
|
||||
getAdjustedRowIndex,
|
||||
getGroupRenderData,
|
||||
getRowItem,
|
||||
groupHeaderInfoByRowIndex,
|
||||
parsedColumns,
|
||||
pinnedLeftColumnCount,
|
||||
pinnedLeftColumnWidths,
|
||||
pinnedRightColumnCount,
|
||||
pinnedRightColumnWidths,
|
||||
setAlbumGroupContentHeight,
|
||||
tableConfig,
|
||||
],
|
||||
);
|
||||
|
||||
const pinnedLeftGridMinWidthPx = useMemo(() => {
|
||||
@@ -729,6 +734,8 @@ const MemoizedVirtualizedTableGrid = memo(VirtualizedTableGrid, (prevProps, next
|
||||
prevProps.calculatedColumnWidths,
|
||||
nextProps.calculatedColumnWidths,
|
||||
) &&
|
||||
prevProps.albumGroupContentHeights === nextProps.albumGroupContentHeights &&
|
||||
prevProps.setAlbumGroupContentHeight === nextProps.setAlbumGroupContentHeight &&
|
||||
prevProps.tableConfig === nextProps.tableConfig &&
|
||||
prevProps.data === nextProps.data &&
|
||||
prevProps.dataWithGroups === nextProps.dataWithGroups &&
|
||||
@@ -766,6 +773,7 @@ export interface TableGroupHeader {
|
||||
|
||||
export interface TableItemProps {
|
||||
adjustedRowIndexMap?: Map<number, number>;
|
||||
albumGroupContentHeights?: Map<number, number>;
|
||||
albumGroupImageSize?: number;
|
||||
calculatedColumnWidths?: number[];
|
||||
cellPadding?: ItemTableListProps['cellPadding'];
|
||||
@@ -799,6 +807,7 @@ export interface TableItemProps {
|
||||
pinnedRightColumnWidths?: number[];
|
||||
playerContext: PlayerContext;
|
||||
playlistId?: string;
|
||||
setAlbumGroupContentHeight?: (rowIndex: number, height: number) => void;
|
||||
size?: ItemTableListProps['size'];
|
||||
startRowIndex?: number;
|
||||
tableId: string;
|
||||
@@ -1286,6 +1295,23 @@ const BaseItemTableList = ({
|
||||
const tableId = useId();
|
||||
const albumGroupImageSize = useAlbumGroupImageSize();
|
||||
const baseItemCount = itemCount ?? data.length;
|
||||
const [albumGroupContentHeights, setAlbumGroupContentHeights] = useState(
|
||||
() => new Map<number, number>(),
|
||||
);
|
||||
|
||||
const setAlbumGroupContentHeight = useCallback((rowIndex: number, height: number) => {
|
||||
setAlbumGroupContentHeights((prev) => {
|
||||
if (prev.get(rowIndex) === height) return prev;
|
||||
const next = new Map(prev);
|
||||
next.set(rowIndex, height);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setAlbumGroupContentHeights(new Map());
|
||||
}, [baseItemCount, data]);
|
||||
|
||||
const totalItemCount = enableHeader ? baseItemCount + 1 : baseItemCount;
|
||||
const [centerContainerWidth, setCenterContainerWidth] = useState(0);
|
||||
const [totalContainerWidth, setTotalContainerWidth] = useState(0);
|
||||
@@ -1444,12 +1470,10 @@ const BaseItemTableList = ({
|
||||
return headerHeight;
|
||||
}
|
||||
|
||||
// When an album image is enlarged beyond the album group's combined
|
||||
// row height, grow the group's LAST row to reserve the leftover
|
||||
// space (so the following album isn't clipped). Other rows keep
|
||||
// their standard height.
|
||||
// Grow the group's LAST row when the album image or wrapped album
|
||||
// info (title + artists + controls) needs more than the standard
|
||||
// combined row height. Other rows keep their standard height.
|
||||
if (
|
||||
albumGroupImageSize > baseHeight &&
|
||||
cellProps?.hasAlbumGroupColumn &&
|
||||
isLastInAlbumGroup(
|
||||
index,
|
||||
@@ -1466,7 +1490,20 @@ const BaseItemTableList = ({
|
||||
cellProps.enableHeader,
|
||||
cellProps.data.length,
|
||||
);
|
||||
const lastRowHeight = albumGroupImageSize - (groupRowCount - 1) * baseHeight;
|
||||
const groupStartRowIndex = getAlbumGroupStartRowIndex(
|
||||
index,
|
||||
cellProps.getRowItem,
|
||||
cellProps.enableHeader,
|
||||
);
|
||||
const contentHeight =
|
||||
cellProps.albumGroupContentHeights?.get(groupStartRowIndex) ?? 0;
|
||||
const totalGroupHeight = getAlbumGroupSpanHeight(
|
||||
groupRowCount,
|
||||
baseHeight,
|
||||
albumGroupImageSize,
|
||||
contentHeight,
|
||||
);
|
||||
const lastRowHeight = totalGroupHeight - (groupRowCount - 1) * baseHeight;
|
||||
if (lastRowHeight > baseHeight) {
|
||||
return lastRowHeight;
|
||||
}
|
||||
@@ -1775,6 +1812,7 @@ const BaseItemTableList = ({
|
||||
totalColumnCount={totalColumnCount}
|
||||
/>
|
||||
<MemoizedVirtualizedTableGrid
|
||||
albumGroupContentHeights={albumGroupContentHeights}
|
||||
calculatedColumnWidths={displayColumnWidths}
|
||||
CellComponent={optimizedCellComponent}
|
||||
data={data}
|
||||
@@ -1792,6 +1830,7 @@ const BaseItemTableList = ({
|
||||
pinnedRowCount={pinnedRowCount}
|
||||
pinnedRowRef={pinnedRowRef}
|
||||
scrollShadowStore={scrollShadowStore}
|
||||
setAlbumGroupContentHeight={setAlbumGroupContentHeight}
|
||||
tableConfig={tableConfigValue}
|
||||
totalColumnCount={totalColumnCount}
|
||||
totalRowCount={totalRowCount}
|
||||
|
||||
Reference in New Issue
Block a user