add Title (artist) column (#1496)

This commit is contained in:
jeffvli
2026-01-17 01:49:59 -08:00
parent d1aed5007f
commit 18d56f32cf
8 changed files with 345 additions and 4 deletions
@@ -0,0 +1,59 @@
.title-artist {
display: flex;
gap: var(--theme-spacing-sm);
width: 100%;
height: 100%;
}
.text-container {
display: grid;
grid-template-rows: 1fr 1fr;
gap: var(--theme-spacing-xs);
min-width: 0;
}
.text-container.align-left {
text-align: left;
}
.text-container.align-center {
text-align: center;
}
.text-container.align-right {
text-align: right;
}
.text-container.compact {
gap: 0;
}
.title {
display: inline-block;
width: 100%;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.artists {
display: block;
overflow: hidden;
text-overflow: ellipsis;
font-size: var(--theme-font-size-xs) !important;
color: var(--theme-colors-foreground-muted);
white-space: nowrap;
user-select: none;
}
.folder-icon {
color: black;
fill: rgb(255 215 100);
}
.active {
color: var(--theme-colors-primary);
}
@@ -0,0 +1,209 @@
import clsx from 'clsx';
import { CSSProperties } from 'react';
import { Link } from 'react-router';
import styles from './title-artist-column.module.css';
import { getTitlePath } from '/@/renderer/components/item-list/helpers/get-title-path';
import {
ColumnNullFallback,
ColumnSkeletonVariable,
ItemTableListInnerColumn,
TableColumnContainer,
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
import { useIsActiveRow } from '/@/renderer/components/item-list/item-table-list/item-table-list-context';
import { JoinedArtists } from '/@/renderer/features/albums/components/joined-artists';
import { Icon } from '/@/shared/components/icon/icon';
import { Text } from '/@/shared/components/text/text';
import { Folder, LibraryItem, QueueSong } from '/@/shared/types/domain-types';
export const DefaultTitleArtistColumn = (props: ItemTableListInnerColumn) => {
const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
const row: object | undefined = (rowItem as any)?.id;
const item = rowItem as any;
const align = props.columns[props.columnIndex]?.align || 'start';
if (item && 'name' in item && 'artists' in item) {
const rowHeight = props.getRowHeight(props.rowIndex, props);
const path = getTitlePath(props.itemType, (rowItem as any).id as string);
const item = rowItem as any;
const titleLinkProps = path
? {
component: Link,
isLink: true,
state: { item },
to: path,
}
: {};
return (
<TableColumnContainer
className={clsx(styles.titleArtist)}
containerStyle={{ '--row-height': `${rowHeight}px` } as CSSProperties}
{...props}
>
<div
className={clsx(styles.textContainer, {
[styles.alignCenter]: align === 'center',
[styles.alignLeft]: align === 'start',
[styles.alignRight]: align === 'end',
[styles.compact]: props.size === 'compact',
})}
>
<Text className={styles.title} isNoSelect size="md" {...titleLinkProps}>
{item.name as string}
</Text>
<div className={styles.artists}>
<JoinedArtists
artistName={item.albumArtist}
artists={item.albumArtists}
linkProps={{ fw: 400, isMuted: true }}
rootTextProps={{ fw: 400, isMuted: true, size: 'sm' }}
/>
</div>
</div>
</TableColumnContainer>
);
}
if (row === null) {
return <ColumnNullFallback {...props} />;
}
return <ColumnSkeletonVariable {...props} />;
};
export const QueueSongTitleArtistColumn = (props: ItemTableListInnerColumn) => {
const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
const row: object | undefined = rowItem as any;
const song = rowItem as QueueSong;
const isActive = useIsActiveRow(song?.id, song?._uniqueId);
const align = props.columns[props.columnIndex]?.align || 'start';
const alignClass =
align === 'center' ? 'align-center' : align === 'end' ? 'align-right' : 'align-left';
if (row && 'name' in row && 'artists' in row) {
const rowHeight = props.getRowHeight(props.rowIndex, props);
const path = getTitlePath(props.itemType, (rowItem as any).id as string);
const item = rowItem as any;
const titleLinkProps = path
? {
component: Link,
isLink: true,
state: { item },
to: path,
}
: {};
return (
<TableColumnContainer
className={clsx(styles.titleArtist, styles[alignClass])}
containerStyle={{ '--row-height': `${rowHeight}px` } as CSSProperties}
{...props}
>
<div
className={clsx(styles.textContainer, styles[alignClass], {
[styles.active]: isActive,
[styles.compact]: props.size === 'compact',
})}
>
<Text
className={clsx({
[styles.active]: isActive,
[styles.title]: true,
})}
isNoSelect
size="md"
{...titleLinkProps}
>
{row.name as string}
{song?.trackSubtitle && props.itemType !== LibraryItem.QUEUE_SONG && (
<Text
className={clsx({
[styles.active]: isActive,
})}
component="span"
isMuted
size="sm"
>
{' ('}
{song.trackSubtitle}
{')'}
</Text>
)}
</Text>
<div className={styles.artists}>
<JoinedArtists
artistName={item.artistName}
artists={item.artists}
linkProps={{ fw: 400, isMuted: true }}
rootTextProps={{ fw: 400, isMuted: true, size: 'sm' }}
/>
</div>
</div>
</TableColumnContainer>
);
}
if ((rowItem as unknown as Folder)?._itemType === LibraryItem.FOLDER) {
const rowHeight = props.getRowHeight(props.rowIndex, props);
const path = getTitlePath(props.itemType, (rowItem as any).id as string);
const item = rowItem as any;
const textStyles = isActive ? { color: 'var(--theme-colors-primary)' } : {};
const titleLinkProps = path
? {
component: Link,
isLink: true,
state: { item },
to: path,
}
: {};
const title = (rowItem as unknown as Folder)?.name;
return (
<TableColumnContainer
className={clsx(styles.titleArtist, styles[alignClass])}
containerStyle={{ '--row-height': `${rowHeight}px` } as CSSProperties}
{...props}
>
<Icon className={styles.folderIcon} icon="folder" size="2xl" />
<Text
className={styles.title}
isNoSelect
size="md"
{...titleLinkProps}
style={textStyles}
>
{title}
</Text>
</TableColumnContainer>
);
}
if (row === null) {
return <ColumnNullFallback {...props} />;
}
return <ColumnSkeletonVariable {...props} />;
};
export const TitleArtistColumn = (props: ItemTableListInnerColumn) => {
const { itemType } = props;
switch (itemType) {
case LibraryItem.FOLDER:
case LibraryItem.PLAYLIST_SONG:
case LibraryItem.QUEUE_SONG:
case LibraryItem.SONG:
return <QueueSongTitleArtistColumn {...props} />;
default:
return <DefaultTitleArtistColumn {...props} />;
}
};