add vertical album group layout

This commit is contained in:
jeffvli
2026-07-14 15:50:08 -07:00
parent 14e66c3ec9
commit 76b7ac8cec
9 changed files with 131 additions and 50 deletions
+1
View File
@@ -1233,6 +1233,7 @@
"albumGroupConfig": "Album Group configuration", "albumGroupConfig": "Album Group configuration",
"albumGroupMetadata": "Metadata fields", "albumGroupMetadata": "Metadata fields",
"albumGroupShowFavoriteRating": "Show favorites and ratings", "albumGroupShowFavoriteRating": "Show favorites and ratings",
"albumGroupVerticalLayout": "Vertical layout",
"albumImageSize": "Album image size", "albumImageSize": "Album image size",
"autoFitColumns": "Auto fit columns", "autoFitColumns": "Auto fit columns",
"autosize": "Autosize", "autosize": "Autosize",
@@ -2,9 +2,22 @@
display: flex; display: flex;
gap: var(--theme-spacing-xs); gap: var(--theme-spacing-xs);
align-items: center; align-items: center;
min-height: 22px; min-height: calc(1.875rem * var(--mantine-scale, 1));
} }
.hidden { .favorite {
visibility: hidden; width: auto !important;
min-width: 0 !important;
height: calc(1.875rem * var(--mantine-scale, 1)) !important;
min-height: calc(1.875rem * var(--mantine-scale, 1)) !important;
padding-inline: var(--theme-spacing-xs);
}
.rating {
box-sizing: border-box;
display: flex;
align-items: center;
height: calc(1.875rem * var(--mantine-scale, 1));
padding-block: var(--theme-spacing-xs);
padding-inline: var(--theme-spacing-xs) !important;
} }
@@ -29,17 +29,11 @@ const useAlbumGroupAlbum = (albumId: string | undefined, serverId: string | unde
interface AlbumGroupControlsProps { interface AlbumGroupControlsProps {
albumId: string | undefined; albumId: string | undefined;
isGroupHovered: boolean;
serverId: string | undefined; serverId: string | undefined;
serverType: ServerType | undefined; serverType: ServerType | undefined;
} }
export const AlbumGroupControls = ({ export const AlbumGroupControls = ({ albumId, serverId, serverType }: AlbumGroupControlsProps) => {
albumId,
isGroupHovered,
serverId,
serverType,
}: AlbumGroupControlsProps) => {
const showRatingsSetting = useShowRatings(); const showRatingsSetting = useShowRatings();
const detailQuery = useAlbumGroupAlbum(albumId, serverId); const detailQuery = useAlbumGroupAlbum(albumId, serverId);
const setFavorite = useSetFavorite(); const setFavorite = useSetFavorite();
@@ -84,13 +78,13 @@ export const AlbumGroupControls = ({
return ( return (
<div className={styles.controls}> <div className={styles.controls}>
<ActionIcon <ActionIcon
className={album.userFavorite || isGroupHovered ? undefined : styles.hidden} className={styles.favorite}
disabled={isMutatingFavorite} disabled={isMutatingFavorite}
icon="favorite" icon="favorite"
iconProps={{ iconProps={{
color: album.userFavorite ? 'primary' : 'muted', color: album.userFavorite ? 'primary' : 'muted',
fill: album.userFavorite ? 'primary' : undefined, fill: album.userFavorite ? 'primary' : undefined,
size: 'md', size: 'xs',
}} }}
onClick={handleFavorite} onClick={handleFavorite}
onDoubleClick={(event) => { onDoubleClick={(event) => {
@@ -98,11 +92,11 @@ export const AlbumGroupControls = ({
event.preventDefault(); event.preventDefault();
}} }}
size="xs" size="xs"
variant="subtle" variant="transparent"
/> />
{showRating && ( {showRating && (
<Rating <Rating
className={album.userRating || isGroupHovered ? undefined : styles.hidden} className={styles.rating}
onChange={handleRating} onChange={handleRating}
readOnly={isMutatingRating} readOnly={isMutatingRating}
size="xs" size="xs"
@@ -7,6 +7,11 @@
padding: 0 var(--theme-spacing-xs); padding: 0 var(--theme-spacing-xs);
} }
.container.vertical {
flex-direction: column;
align-items: center;
}
.image-container { .image-container {
position: relative; position: relative;
box-sizing: border-box; box-sizing: border-box;
@@ -50,8 +55,16 @@
padding: 0; padding: 0;
} }
.info.enlarged-image { .container.vertical .image-container {
padding-top: var(--theme-spacing-xs); align-self: center;
width: auto;
height: auto;
}
.container.vertical .info {
align-items: center;
width: 100%;
text-align: center;
} }
.album-title { .album-title {
@@ -74,6 +87,6 @@
.controls-row { .controls-row {
flex-shrink: 0; flex-shrink: 0;
min-height: 22px; min-height: calc(1.875rem * var(--mantine-scale, 1));
margin-top: var(--theme-spacing-xxs); margin-top: var(--theme-spacing-xs);
} }
@@ -24,6 +24,7 @@ import {
useAlbumGroupImageSize, useAlbumGroupImageSize,
useAlbumGroupItems, useAlbumGroupItems,
useAlbumGroupShowFavoriteRating, useAlbumGroupShowFavoriteRating,
useAlbumGroupVerticalLayout,
usePlayButtonBehavior, usePlayButtonBehavior,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
@@ -54,7 +55,8 @@ export const AlbumGroupHeader = ({
const { t } = useTranslation(); const { t } = useTranslation();
const albumGroupItems = useAlbumGroupItems(); const albumGroupItems = useAlbumGroupItems();
const showFavoriteRating = useAlbumGroupShowFavoriteRating(); const showFavoriteRating = useAlbumGroupShowFavoriteRating();
const [isHovered, setIsHovered] = useState(false); const isVerticalLayout = useAlbumGroupVerticalLayout();
const [isImageHovered, setIsImageHovered] = useState(false);
const [resolved, setResolved] = useState<null | { forInfoHeight: number; height: number }>( const [resolved, setResolved] = useState<null | { forInfoHeight: number; height: number }>(
null, null,
); );
@@ -80,10 +82,12 @@ export const AlbumGroupHeader = ({
.filter((item) => item.content != null); .filter((item) => item.content != null);
}, [albumGroupItems, metadata, song, t]); }, [albumGroupItems, metadata, song, t]);
// The album group spans the combined row height, but when the image is // Horizontal: info floor is max(image, row span) so metadata aligns with the image.
// enlarged the group's last row is grown so the total reaches the img size. // Vertical: no minHeight floor — row-span minHeight made scrollHeight report the full
// track span (e.g. 15×40=600) instead of natural text height, then image+info overflowed
// the reserved group height and made the virtualizer thrash at the scroll bottom.
const infoHeight = const infoHeight =
groupRowCount !== undefined groupRowCount !== undefined && !isVerticalLayout
? albumImageSize > 0 ? albumImageSize > 0
? Math.max(albumImageSize, groupRowCount * rowHeight) ? Math.max(albumImageSize, groupRowCount * rowHeight)
: groupRowCount * rowHeight : groupRowCount * rowHeight
@@ -101,8 +105,6 @@ export const AlbumGroupHeader = ({
? { ? {
aspectRatio: 'auto', aspectRatio: 'auto',
height: `${albumImageSize}px`, height: `${albumImageSize}px`,
paddingBottom: 'var(--theme-spacing-xs)',
paddingTop: 'var(--theme-spacing-xs)',
position: 'relative' as const, position: 'relative' as const,
width: `${albumImageSize}px`, width: `${albumImageSize}px`,
zIndex: 1, zIndex: 1,
@@ -120,7 +122,12 @@ export const AlbumGroupHeader = ({
const resolvedHeight = Math.max(infoHeight ?? 0, contentHeight); const resolvedHeight = Math.max(infoHeight ?? 0, contentHeight);
if (infoHeight !== undefined) { if (infoHeight !== undefined) {
setResolved({ forInfoHeight: infoHeight, height: resolvedHeight }); setResolved((prev) => {
if (prev?.forInfoHeight === infoHeight && prev.height === resolvedHeight) {
return prev;
}
return { forInfoHeight: infoHeight, height: resolvedHeight };
});
} }
// Only persist heights that exceed the image/row floor. Equal values // Only persist heights that exceed the image/row floor. Equal values
@@ -145,18 +152,21 @@ export const AlbumGroupHeader = ({
groupKey, groupKey,
groupRowCount, groupRowCount,
infoHeight, infoHeight,
isVerticalLayout,
metadataRows.length, metadataRows.length,
setAlbumGroupContentHeight, setAlbumGroupContentHeight,
showFavoriteRating,
storedContentHeight, storedContentHeight,
]); ]);
return ( return (
<div <div className={clsx(styles.container, isVerticalLayout && styles.vertical)}>
className={styles.container} <div
onMouseEnter={() => setIsHovered(true)} className={styles.imageContainer}
onMouseLeave={() => setIsHovered(false)} onMouseEnter={() => setIsImageHovered(true)}
> onMouseLeave={() => setIsImageHovered(false)}
<div className={styles.imageContainer} style={imageContainerStyle}> style={imageContainerStyle}
>
<ItemImage <ItemImage
className={imageColumnStyles.compactImage} className={imageColumnStyles.compactImage}
enableDebounce enableDebounce
@@ -166,7 +176,7 @@ export const AlbumGroupHeader = ({
src={song?.imageUrl} src={song?.imageUrl}
type="table" type="table"
/> />
{isHovered && onPlay && ( {isImageHovered && onPlay && (
<div className={imageColumnStyles.playButtonOverlay}> <div className={imageColumnStyles.playButtonOverlay}>
<PlayTooltip type={playButtonBehavior}> <PlayTooltip type={playButtonBehavior}>
<PlayButton <PlayButton
@@ -185,7 +195,7 @@ export const AlbumGroupHeader = ({
)} )}
</div> </div>
<div <div
className={clsx(styles.info, albumImageSize > 0 && styles.enlargedImage)} className={styles.info}
ref={infoRef} ref={infoRef}
style={{ minHeight: resolvedInfoHeight ?? infoHeight }} style={{ minHeight: resolvedInfoHeight ?? infoHeight }}
> >
@@ -213,7 +223,6 @@ export const AlbumGroupHeader = ({
<div className={styles.controlsRow}> <div className={styles.controlsRow}>
<AlbumGroupControls <AlbumGroupControls
albumId={song?.albumId} albumId={song?.albumId}
isGroupHovered={isHovered}
serverId={song?._serverId} serverId={song?._serverId}
serverType={song?._serverType} serverType={song?._serverType}
/> />
@@ -391,22 +391,18 @@ const NonMutedColumns = [TableColumn.TITLE, TableColumn.TITLE_ARTIST, TableColum
* Keep in sync with album-group-header styles (title line-clamp, metadata xs, controls). * Keep in sync with album-group-header styles (title line-clamp, metadata xs, controls).
*/ */
export function estimateAlbumGroupContentHeight({ export function estimateAlbumGroupContentHeight({
hasEnlargedImage,
metadataRowCount, metadataRowCount,
showControls, showControls,
}: { }: {
hasEnlargedImage: boolean;
metadataRowCount: number; metadataRowCount: number;
showControls: boolean; showControls: boolean;
}): number { }): number {
const TITLE_LINE_HEIGHT = 20; const TITLE_LINE_HEIGHT = 20;
const TITLE_MAX_LINES = 3; const TITLE_MAX_LINES = 3;
const METADATA_LINE_HEIGHT = 18; const METADATA_LINE_HEIGHT = 18;
const CONTROLS_HEIGHT = 26; const CONTROLS_HEIGHT = 38;
const PADDING_TOP = hasEnlargedImage ? 8 : 0;
return ( return (
PADDING_TOP +
TITLE_LINE_HEIGHT * TITLE_MAX_LINES + TITLE_LINE_HEIGHT * TITLE_MAX_LINES +
Math.max(0, metadataRowCount) * METADATA_LINE_HEIGHT + Math.max(0, metadataRowCount) * METADATA_LINE_HEIGHT +
(showControls ? CONTROLS_HEIGHT : 0) (showControls ? CONTROLS_HEIGHT : 0)
@@ -459,13 +455,23 @@ export function getAlbumGroupRowCount(
return end - start + 1; return end - start + 1;
} }
export const ALBUM_GROUP_STACK_GAP = 12;
export function getAlbumGroupSpanHeight( export function getAlbumGroupSpanHeight(
groupRowCount: number, groupRowCount: number,
baseHeight: number, baseHeight: number,
albumGroupImageSize: number, albumGroupImageSize: number,
contentHeight = 0, contentHeight = 0,
options?: { isVertical?: boolean },
): number { ): number {
const rowSpanHeight = groupRowCount * baseHeight; const rowSpanHeight = groupRowCount * baseHeight;
const isVertical = options?.isVertical ?? false;
if (isVertical) {
const imageSize = albumGroupImageSize > 0 ? albumGroupImageSize : 96;
return Math.max(rowSpanHeight, imageSize + ALBUM_GROUP_STACK_GAP + contentHeight);
}
const imageSpanHeight = const imageSpanHeight =
albumGroupImageSize > 0 ? Math.max(albumGroupImageSize, rowSpanHeight) : rowSpanHeight; albumGroupImageSize > 0 ? Math.max(albumGroupImageSize, rowSpanHeight) : rowSpanHeight;
@@ -565,9 +571,6 @@ function ClampedCell({
// standard height and the reserved space below is left empty (uniform // standard height and the reserved space below is left empty (uniform
// background) for the overflowing album image. // background) for the overflowing album image.
function getAlbumGroupClampHeight(props: ItemTableListInnerColumn): null | number { function getAlbumGroupClampHeight(props: ItemTableListInnerColumn): null | number {
const albumImageSize = props.albumGroupImageSize ?? 0;
if (albumImageSize <= 0) return null;
if (props.type === TableColumn.ALBUM_GROUP) return null; if (props.type === TableColumn.ALBUM_GROUP) return null;
if (!isAlbumGroupingActive(props.columns)) return null; if (!isAlbumGroupingActive(props.columns)) return null;
@@ -583,6 +586,8 @@ function getAlbumGroupClampHeight(props: ItemTableListInnerColumn): null | numbe
return null; return null;
} }
const albumImageSize = props.albumGroupImageSize ?? 0;
const isVertical = props.albumGroupVerticalLayout ?? false;
const baseHeight = baseRowHeightForSize(props.size); const baseHeight = baseRowHeightForSize(props.size);
const groupRowCount = getAlbumGroupRowCount( const groupRowCount = getAlbumGroupRowCount(
props.rowIndex, props.rowIndex,
@@ -600,12 +605,18 @@ function getAlbumGroupClampHeight(props: ItemTableListInnerColumn): null | numbe
const measuredContentHeight = groupHeightKey const measuredContentHeight = groupHeightKey
? props.albumGroupContentHeights?.get(groupHeightKey) ? props.albumGroupContentHeights?.get(groupHeightKey)
: undefined; : undefined;
const contentHeight = measuredContentHeight ?? props.estimatedAlbumGroupContentHeight ?? 0; // Never reserve less than the estimate — early measure can miss the async
// favorites/ratings row (AlbumGroupControls returns null until album loads).
const contentHeight = Math.max(
measuredContentHeight ?? 0,
props.estimatedAlbumGroupContentHeight ?? 0,
);
const totalGroupHeight = getAlbumGroupSpanHeight( const totalGroupHeight = getAlbumGroupSpanHeight(
groupRowCount, groupRowCount,
baseHeight, baseHeight,
albumImageSize, albumImageSize,
contentHeight, contentHeight,
{ isVertical },
); );
// Only clamp when the row was actually grown to fit the image or wrapped text. // Only clamp when the row was actually grown to fit the image or wrapped text.
@@ -78,6 +78,7 @@ import {
useAlbumGroupImageSize, useAlbumGroupImageSize,
useAlbumGroupItems, useAlbumGroupItems,
useAlbumGroupShowFavoriteRating, useAlbumGroupShowFavoriteRating,
useAlbumGroupVerticalLayout,
usePlayerStore, usePlayerStore,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { animationProps } from '/@/shared/components/animations/animation-props'; import { animationProps } from '/@/shared/components/animations/animation-props';
@@ -235,6 +236,7 @@ const VirtualizedTableGrid = ({
}: VirtualizedTableGridProps) => { }: VirtualizedTableGridProps) => {
const { enableHeader, enableRowHoverHighlight, getRowHeight, groups } = tableConfig; const { enableHeader, enableRowHoverHighlight, getRowHeight, groups } = tableConfig;
const albumGroupImageSize = useAlbumGroupImageSize(); const albumGroupImageSize = useAlbumGroupImageSize();
const albumGroupVerticalLayout = useAlbumGroupVerticalLayout();
const hoverDelegateRef = useRef<HTMLDivElement | null>(null); const hoverDelegateRef = useRef<HTMLDivElement | null>(null);
useRowInteractionDelegate({ useRowInteractionDelegate({
@@ -396,6 +398,7 @@ const VirtualizedTableGrid = ({
() => ({ () => ({
albumGroupContentHeights, albumGroupContentHeights,
albumGroupImageSize, albumGroupImageSize,
albumGroupVerticalLayout,
calculatedColumnWidths, calculatedColumnWidths,
cellPadding: tableConfig.cellPadding, cellPadding: tableConfig.cellPadding,
columns: tableConfig.columns, columns: tableConfig.columns,
@@ -435,6 +438,7 @@ const VirtualizedTableGrid = ({
[ [
albumGroupContentHeights, albumGroupContentHeights,
albumGroupImageSize, albumGroupImageSize,
albumGroupVerticalLayout,
calculatedColumnWidths, calculatedColumnWidths,
dataWithGroups, dataWithGroups,
estimatedAlbumGroupContentHeight, estimatedAlbumGroupContentHeight,
@@ -746,8 +750,7 @@ const MemoizedVirtualizedTableGrid = memo(VirtualizedTableGrid, (prevProps, next
nextProps.calculatedColumnWidths, nextProps.calculatedColumnWidths,
) && ) &&
prevProps.albumGroupContentHeights === nextProps.albumGroupContentHeights && prevProps.albumGroupContentHeights === nextProps.albumGroupContentHeights &&
prevProps.estimatedAlbumGroupContentHeight === prevProps.estimatedAlbumGroupContentHeight === nextProps.estimatedAlbumGroupContentHeight &&
nextProps.estimatedAlbumGroupContentHeight &&
prevProps.setAlbumGroupContentHeight === nextProps.setAlbumGroupContentHeight && prevProps.setAlbumGroupContentHeight === nextProps.setAlbumGroupContentHeight &&
prevProps.tableConfig === nextProps.tableConfig && prevProps.tableConfig === nextProps.tableConfig &&
prevProps.data === nextProps.data && prevProps.data === nextProps.data &&
@@ -788,7 +791,7 @@ export interface TableItemProps {
adjustedRowIndexMap?: Map<number, number>; adjustedRowIndexMap?: Map<number, number>;
albumGroupContentHeights?: Map<string, number>; albumGroupContentHeights?: Map<string, number>;
albumGroupImageSize?: number; albumGroupImageSize?: number;
estimatedAlbumGroupContentHeight?: number; albumGroupVerticalLayout?: boolean;
calculatedColumnWidths?: number[]; calculatedColumnWidths?: number[];
cellPadding?: ItemTableListProps['cellPadding']; cellPadding?: ItemTableListProps['cellPadding'];
columns: ItemTableListColumnConfig[]; columns: ItemTableListColumnConfig[];
@@ -805,6 +808,7 @@ export interface TableItemProps {
enableRowHoverHighlight?: ItemTableListProps['enableRowHoverHighlight']; enableRowHoverHighlight?: ItemTableListProps['enableRowHoverHighlight'];
enableSelection?: ItemTableListProps['enableSelection']; enableSelection?: ItemTableListProps['enableSelection'];
enableVerticalBorders?: ItemTableListProps['enableVerticalBorders']; enableVerticalBorders?: ItemTableListProps['enableVerticalBorders'];
estimatedAlbumGroupContentHeight?: number;
getAdjustedRowIndex?: (rowIndex: number) => number; getAdjustedRowIndex?: (rowIndex: number) => number;
getGroupRenderData?: () => unknown[]; getGroupRenderData?: () => unknown[];
getRowHeight: (index: number, cellProps: TableItemProps) => number; getRowHeight: (index: number, cellProps: TableItemProps) => number;
@@ -1312,6 +1316,7 @@ const BaseItemTableList = ({
const albumGroupImageSize = useAlbumGroupImageSize(); const albumGroupImageSize = useAlbumGroupImageSize();
const albumGroupItems = useAlbumGroupItems(); const albumGroupItems = useAlbumGroupItems();
const albumGroupShowFavoriteRating = useAlbumGroupShowFavoriteRating(); const albumGroupShowFavoriteRating = useAlbumGroupShowFavoriteRating();
const albumGroupVerticalLayout = useAlbumGroupVerticalLayout();
const albumGroupMetadataRowCount = useMemo( const albumGroupMetadataRowCount = useMemo(
() => albumGroupItems.filter((item) => !item.disabled).length, () => albumGroupItems.filter((item) => !item.disabled).length,
[albumGroupItems], [albumGroupItems],
@@ -1319,11 +1324,10 @@ const BaseItemTableList = ({
const estimatedAlbumGroupContentHeight = useMemo( const estimatedAlbumGroupContentHeight = useMemo(
() => () =>
estimateAlbumGroupContentHeight({ estimateAlbumGroupContentHeight({
hasEnlargedImage: (albumGroupImageSize || 96) > 0,
metadataRowCount: albumGroupMetadataRowCount, metadataRowCount: albumGroupMetadataRowCount,
showControls: albumGroupShowFavoriteRating, showControls: albumGroupShowFavoriteRating,
}), }),
[albumGroupImageSize, albumGroupMetadataRowCount, albumGroupShowFavoriteRating], [albumGroupMetadataRowCount, albumGroupShowFavoriteRating],
); );
const baseItemCount = itemCount ?? data.length; const baseItemCount = itemCount ?? data.length;
const [albumGroupContentHeights, setAlbumGroupContentHeights] = useState( const [albumGroupContentHeights, setAlbumGroupContentHeights] = useState(
@@ -1341,7 +1345,7 @@ const BaseItemTableList = ({
useEffect(() => { useEffect(() => {
setAlbumGroupContentHeights(new Map()); setAlbumGroupContentHeights(new Map());
}, [baseItemCount]); }, [albumGroupShowFavoriteRating, albumGroupVerticalLayout, baseItemCount]);
const totalItemCount = enableHeader ? baseItemCount + 1 : baseItemCount; const totalItemCount = enableHeader ? baseItemCount + 1 : baseItemCount;
const [centerContainerWidth, setCenterContainerWidth] = useState(0); const [centerContainerWidth, setCenterContainerWidth] = useState(0);
@@ -1496,12 +1500,16 @@ const BaseItemTableList = ({
: undefined; : undefined;
// Prefer measured height when present; otherwise reserve with a stable // Prefer measured height when present; otherwise reserve with a stable
// estimate so newly virtualized groups do not jump after mount measure. // estimate so newly virtualized groups do not jump after mount measure.
const contentHeight = measuredContentHeight ?? estimatedAlbumGroupContentHeight; const contentHeight = Math.max(
measuredContentHeight ?? 0,
estimatedAlbumGroupContentHeight,
);
const totalGroupHeight = getAlbumGroupSpanHeight( const totalGroupHeight = getAlbumGroupSpanHeight(
groupRowCount, groupRowCount,
baseHeight, baseHeight,
albumGroupImageSize, albumGroupImageSize,
contentHeight, contentHeight,
{ isVertical: albumGroupVerticalLayout },
); );
const lastRowHeight = totalGroupHeight - (groupRowCount - 1) * baseHeight; const lastRowHeight = totalGroupHeight - (groupRowCount - 1) * baseHeight;
if (lastRowHeight > baseHeight) { if (lastRowHeight > baseHeight) {
@@ -1514,6 +1522,7 @@ const BaseItemTableList = ({
}, },
[ [
albumGroupImageSize, albumGroupImageSize,
albumGroupVerticalLayout,
enableHeader, enableHeader,
estimatedAlbumGroupContentHeight, estimatedAlbumGroupContentHeight,
headerHeight, headerHeight,
@@ -1527,6 +1536,7 @@ const BaseItemTableList = ({
() => ({ () => ({
albumGroupContentHeights, albumGroupContentHeights,
albumGroupImageSize, albumGroupImageSize,
albumGroupVerticalLayout,
cellPadding, cellPadding,
columns: parsedColumns, columns: parsedColumns,
controls: {} as ItemControls, controls: {} as ItemControls,
@@ -1562,6 +1572,7 @@ const BaseItemTableList = ({
[ [
albumGroupContentHeights, albumGroupContentHeights,
albumGroupImageSize, albumGroupImageSize,
albumGroupVerticalLayout,
cellPadding, cellPadding,
dataWithGroups, dataWithGroups,
enableAlternateRowColors, enableAlternateRowColors,
@@ -78,6 +78,9 @@ export const TableConfig = ({
const albumGroupShowFavoriteRating = useSettingsStore( const albumGroupShowFavoriteRating = useSettingsStore(
(state) => state.general.albumGroupShowFavoriteRating, (state) => state.general.albumGroupShowFavoriteRating,
); );
const albumGroupVerticalLayout = useSettingsStore(
(state) => state.general.albumGroupVerticalLayout,
);
const imageResTable = useSettingsStore((state) => state.general.imageRes.table); const imageResTable = useSettingsStore((state) => state.general.imageRes.table);
const { setList, setSettings } = useSettingsStoreActions(); const { setList, setSettings } = useSettingsStoreActions();
const [albumGroupOpen, setAlbumGroupOpen] = useState(false); const [albumGroupOpen, setAlbumGroupOpen] = useState(false);
@@ -185,6 +188,26 @@ export const TableConfig = ({
</Text> </Text>
), ),
}, },
{
component: (
<ListConfigBooleanControl
onChange={(value) =>
setSettings({
general: {
albumGroupVerticalLayout: value,
},
})
}
value={albumGroupVerticalLayout}
/>
),
id: 'albumGroupVerticalLayout',
label: (
<Text fw={500} pl="md" size="sm">
{t('table.config.general.albumGroupVerticalLayout')}
</Text>
),
},
] ]
: []), : []),
] ]
@@ -367,6 +390,7 @@ export const TableConfig = ({
albumGroupOpen, albumGroupOpen,
albumGroupImageSize, albumGroupImageSize,
albumGroupShowFavoriteRating, albumGroupShowFavoriteRating,
albumGroupVerticalLayout,
imageResTable, imageResTable,
setSettings, setSettings,
]); ]);
+5
View File
@@ -488,6 +488,7 @@ export const GeneralSettingsSchema = z.object({
albumGroupImageSize: z.number(), albumGroupImageSize: z.number(),
albumGroupItems: z.array(SortableItemSchema(AlbumGroupItemSchema)), albumGroupItems: z.array(SortableItemSchema(AlbumGroupItemSchema)),
albumGroupShowFavoriteRating: z.boolean(), albumGroupShowFavoriteRating: z.boolean(),
albumGroupVerticalLayout: z.boolean(),
artistBackground: z.boolean(), artistBackground: z.boolean(),
artistBackgroundBlur: z.number(), artistBackgroundBlur: z.number(),
artistItems: z.array(SortableItemSchema(ArtistItemSchema)), artistItems: z.array(SortableItemSchema(ArtistItemSchema)),
@@ -1211,6 +1212,7 @@ const initialState: SettingsState = {
albumGroupImageSize: 0, albumGroupImageSize: 0,
albumGroupItems, albumGroupItems,
albumGroupShowFavoriteRating: true, albumGroupShowFavoriteRating: true,
albumGroupVerticalLayout: true,
artistBackground: true, artistBackground: true,
artistBackgroundBlur: 3, artistBackgroundBlur: 3,
artistItems, artistItems,
@@ -2727,6 +2729,9 @@ export const useAlbumGroupImageSize = () =>
export const useAlbumGroupShowFavoriteRating = () => export const useAlbumGroupShowFavoriteRating = () =>
useSettingsStore((state) => state.general.albumGroupShowFavoriteRating); useSettingsStore((state) => state.general.albumGroupShowFavoriteRating);
export const useAlbumGroupVerticalLayout = () =>
useSettingsStore((state) => state.general.albumGroupVerticalLayout);
export const useVolumeWidth = () => useSettingsStore((state) => state.general.volumeWidth, shallow); export const useVolumeWidth = () => useSettingsStore((state) => state.general.volumeWidth, shallow);
export const useFollowCurrentSong = () => export const useFollowCurrentSong = () =>