mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-15 16:04:19 +02:00
Add album grouping column (#1722)
* Add album grouping column --------- Co-authored-by: jeffvli <jeffvictorli@gmail.com>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { AlbumGroupHeader } from '/@/renderer/components/item-list/item-table-list/album-group-header';
|
||||
import {
|
||||
isLastInAlbumGroup,
|
||||
ItemTableListInnerColumn,
|
||||
TableColumnContainer,
|
||||
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||
import { Song } from '/@/shared/types/domain-types';
|
||||
import { Play } from '/@/shared/types/types';
|
||||
|
||||
export const AlbumGroupColumn = (props: ItemTableListInnerColumn) => {
|
||||
const firstDataRow = props.enableHeader ? 1 : 0;
|
||||
const item = props.getRowItem?.(props.rowIndex) as null | Song | undefined;
|
||||
|
||||
const handlePlay = useCallback(
|
||||
(playType: Play) => {
|
||||
if (!item || !props.controls?.onDoubleClick) return;
|
||||
|
||||
const isHeaderEnabled = !!props.enableHeader;
|
||||
const index = isHeaderEnabled ? props.rowIndex - 1 : props.rowIndex;
|
||||
|
||||
props.controls.onDoubleClick({
|
||||
event: null,
|
||||
index,
|
||||
internalState: (props as any).internalState,
|
||||
item,
|
||||
itemType: props.itemType,
|
||||
meta: { playType },
|
||||
});
|
||||
},
|
||||
[item, props],
|
||||
);
|
||||
|
||||
if (!item?.album) {
|
||||
return <div style={props.style} />;
|
||||
}
|
||||
|
||||
// Check if this is the first row of a new album group (by album name)
|
||||
let isFirstInGroup = true;
|
||||
if (props.rowIndex > firstDataRow) {
|
||||
const prevItem = props.getRowItem?.(props.rowIndex - 1) as null | Song | undefined;
|
||||
// If prevItem is undefined (not loaded yet), assume same group to avoid duplicates
|
||||
if (prevItem === undefined || prevItem?.album === item.album) {
|
||||
isFirstInGroup = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isFirstInGroup) {
|
||||
// For non-first rows, add border-bottom on the last row of the group
|
||||
const needsBorder =
|
||||
props.enableHorizontalBorders &&
|
||||
isLastInAlbumGroup(
|
||||
props.rowIndex,
|
||||
props.getRowItem,
|
||||
!!props.enableHeader,
|
||||
props.data.length,
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...props.style,
|
||||
...(needsBorder
|
||||
? { borderBottom: '1px solid var(--theme-colors-border)' }
|
||||
: {}),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let groupRowCount = 1;
|
||||
const totalDataRows = props.data.length + firstDataRow;
|
||||
for (let idx = props.rowIndex + 1; idx < totalDataRows; idx++) {
|
||||
const nextItem = props.getRowItem?.(idx) as null | Song | undefined;
|
||||
if (!nextItem || nextItem.album !== item.album) break;
|
||||
groupRowCount++;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableColumnContainer {...props} enableAlternateRowColors={false}>
|
||||
<AlbumGroupHeader
|
||||
groupRowCount={groupRowCount}
|
||||
onPlay={handlePlay}
|
||||
size={props.size === 'default' ? 'normal' : props.size}
|
||||
song={item}
|
||||
/>
|
||||
</TableColumnContainer>
|
||||
);
|
||||
};
|
||||
+4
@@ -6,6 +6,10 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.title-combined.no-image {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.text-container {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
|
||||
+87
-71
@@ -81,6 +81,7 @@ export const DefaultTitleCombinedColumn = (props: ItemTableListInnerColumn) => {
|
||||
const rowHeight = props.getRowHeight(props.rowIndex, props);
|
||||
const path = getTitlePath(props.itemType, (rowItem as any).id as string);
|
||||
const align = props.columns[props.columnIndex]?.align || 'start';
|
||||
const hasAlbumGroupColumn = props.hasAlbumGroupColumn ?? false;
|
||||
|
||||
const item = rowItem as any;
|
||||
const titleLinkProps = path
|
||||
@@ -94,46 +95,53 @@ export const DefaultTitleCombinedColumn = (props: ItemTableListInnerColumn) => {
|
||||
|
||||
return (
|
||||
<TableColumnContainer
|
||||
className={styles.titleCombined}
|
||||
className={clsx(styles.titleCombined, {
|
||||
[styles.noImage]: hasAlbumGroupColumn,
|
||||
})}
|
||||
containerStyle={{ '--row-height': `${rowHeight}px` } as CSSProperties}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className={styles.imageContainer}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<ItemImage
|
||||
containerClassName={styles.image}
|
||||
enableDebounce={true}
|
||||
enableViewport={false}
|
||||
explicitStatus={item?.explicitStatus}
|
||||
id={item?.imageId}
|
||||
itemType={item?._itemType}
|
||||
src={item?.imageUrl}
|
||||
type="table"
|
||||
/>
|
||||
{isHovered && (
|
||||
<div
|
||||
className={clsx(styles.playButtonOverlay, {
|
||||
[styles.compactPlayButtonOverlay]: props.size === 'compact',
|
||||
})}
|
||||
>
|
||||
<PlayTooltip
|
||||
disabled={props.itemType === LibraryItem.QUEUE_SONG}
|
||||
type={playButtonBehavior}
|
||||
{!hasAlbumGroupColumn && (
|
||||
<div
|
||||
className={styles.imageContainer}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<ItemImage
|
||||
containerClassName={styles.image}
|
||||
enableDebounce={true}
|
||||
enableViewport={false}
|
||||
explicitStatus={item?.explicitStatus}
|
||||
id={item?.imageId}
|
||||
itemType={item?._itemType}
|
||||
src={item?.imageUrl}
|
||||
type="table"
|
||||
/>
|
||||
{isHovered && (
|
||||
<div
|
||||
className={clsx(styles.playButtonOverlay, {
|
||||
[styles.compactPlayButtonOverlay]: props.size === 'compact',
|
||||
})}
|
||||
>
|
||||
<PlayButton
|
||||
fill
|
||||
onClick={(e) => handlePlay(playButtonBehavior, e)}
|
||||
onLongPress={(e) =>
|
||||
handlePlay(LONG_PRESS_PLAY_BEHAVIOR[playButtonBehavior], e)
|
||||
}
|
||||
/>
|
||||
</PlayTooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<PlayTooltip
|
||||
disabled={props.itemType === LibraryItem.QUEUE_SONG}
|
||||
type={playButtonBehavior}
|
||||
>
|
||||
<PlayButton
|
||||
fill
|
||||
onClick={(e) => handlePlay(playButtonBehavior, e)}
|
||||
onLongPress={(e) =>
|
||||
handlePlay(
|
||||
LONG_PRESS_PLAY_BEHAVIOR[playButtonBehavior],
|
||||
e,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</PlayTooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={clsx(styles.textContainer, {
|
||||
[styles.alignCenter]: align === 'center',
|
||||
@@ -224,6 +232,7 @@ export const QueueSongTitleCombinedColumn = (props: ItemTableListInnerColumn) =>
|
||||
const rowHeight = props.getRowHeight(props.rowIndex, props);
|
||||
const path = getTitlePath(props.itemType, (rowItem as any).id as string);
|
||||
const align = props.columns[props.columnIndex]?.align || 'start';
|
||||
const hasAlbumGroupColumn = props.hasAlbumGroupColumn ?? false;
|
||||
|
||||
const item = rowItem as any;
|
||||
|
||||
@@ -238,45 +247,52 @@ export const QueueSongTitleCombinedColumn = (props: ItemTableListInnerColumn) =>
|
||||
|
||||
return (
|
||||
<TableColumnContainer
|
||||
className={styles.titleCombined}
|
||||
className={clsx(styles.titleCombined, {
|
||||
[styles.noImage]: hasAlbumGroupColumn,
|
||||
})}
|
||||
containerStyle={{ '--row-height': `${rowHeight}px` } as CSSProperties}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className={styles.imageContainer}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<ItemImage
|
||||
containerClassName={styles.image}
|
||||
explicitStatus={item?.explicitStatus}
|
||||
id={item?.imageId}
|
||||
itemType={item?._itemType}
|
||||
serverId={item?._serverId}
|
||||
src={item?.imageUrl}
|
||||
type="table"
|
||||
/>
|
||||
{isHovered && (
|
||||
<div
|
||||
className={clsx(styles.playButtonOverlay, {
|
||||
[styles.compactPlayButtonOverlay]: props.size === 'compact',
|
||||
})}
|
||||
>
|
||||
<PlayTooltip
|
||||
disabled={props.itemType === LibraryItem.QUEUE_SONG}
|
||||
type={playButtonBehavior}
|
||||
{!hasAlbumGroupColumn && (
|
||||
<div
|
||||
className={styles.imageContainer}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<ItemImage
|
||||
containerClassName={styles.image}
|
||||
explicitStatus={item?.explicitStatus}
|
||||
id={item?.imageId}
|
||||
itemType={item?._itemType}
|
||||
serverId={item?._serverId}
|
||||
src={item?.imageUrl}
|
||||
type="table"
|
||||
/>
|
||||
{isHovered && (
|
||||
<div
|
||||
className={clsx(styles.playButtonOverlay, {
|
||||
[styles.compactPlayButtonOverlay]: props.size === 'compact',
|
||||
})}
|
||||
>
|
||||
<PlayButton
|
||||
fill
|
||||
onClick={(e) => handlePlay(playButtonBehavior, e)}
|
||||
onLongPress={(e) =>
|
||||
handlePlay(LONG_PRESS_PLAY_BEHAVIOR[playButtonBehavior], e)
|
||||
}
|
||||
/>
|
||||
</PlayTooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<PlayTooltip
|
||||
disabled={props.itemType === LibraryItem.QUEUE_SONG}
|
||||
type={playButtonBehavior}
|
||||
>
|
||||
<PlayButton
|
||||
fill
|
||||
onClick={(e) => handlePlay(playButtonBehavior, e)}
|
||||
onLongPress={(e) =>
|
||||
handlePlay(
|
||||
LONG_PRESS_PLAY_BEHAVIOR[playButtonBehavior],
|
||||
e,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</PlayTooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={clsx(styles.textContainer, {
|
||||
[styles.active]: isActive,
|
||||
|
||||
Reference in New Issue
Block a user