Files
feishin/src/renderer/components/item-list/item-table-list/columns/title-artist-column.tsx
T
2026-02-07 02:14:24 -08:00

224 lines
8.5 KiB
TypeScript

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 { ExplicitIndicator } from '/@/shared/components/explicit-indicator/explicit-indicator';
import { ExternalSongIndicator } from '/@/shared/components/external-song-indicator/external-song-indicator';
import { Icon } from '/@/shared/components/icon/icon';
import { Text } from '/@/shared/components/text/text';
import { Folder, LibraryItem, QueueSong, ServerType } 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}>
<ExplicitIndicator explicitStatus={item?.explicitStatus} />
<ExternalSongIndicator
isExternal={item?._serverType === ServerType.EXTERNAL}
size="sm"
/>
{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}
>
<ExplicitIndicator explicitStatus={song?.explicitStatus} />
<ExternalSongIndicator
isExternal={song?._serverType === ServerType.EXTERNAL}
size="sm"
/>
{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} />;
};
const TitleArtistColumnBase = (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} />;
}
};
export const TitleArtistColumn = TitleArtistColumnBase;