mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-13 07:42:31 +02:00
add genre image card placeholder
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
|||||||
Album,
|
Album,
|
||||||
AlbumArtist,
|
AlbumArtist,
|
||||||
Artist,
|
Artist,
|
||||||
|
Genre,
|
||||||
LibraryItem,
|
LibraryItem,
|
||||||
Playlist,
|
Playlist,
|
||||||
ServerType,
|
ServerType,
|
||||||
@@ -30,7 +31,7 @@ interface ItemCardControlsProps {
|
|||||||
controls?: ItemControls;
|
controls?: ItemControls;
|
||||||
enableExpansion?: boolean;
|
enableExpansion?: boolean;
|
||||||
internalState?: ItemListStateActions;
|
internalState?: ItemListStateActions;
|
||||||
item: Album | AlbumArtist | Artist | Playlist | Song | undefined;
|
item: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined;
|
||||||
itemType: LibraryItem;
|
itemType: LibraryItem;
|
||||||
showRating: boolean;
|
showRating: boolean;
|
||||||
type?: 'compact' | 'default' | 'poster';
|
type?: 'compact' | 'default' | 'poster';
|
||||||
@@ -60,7 +61,7 @@ const containerProps = {
|
|||||||
const createPlayHandler =
|
const createPlayHandler =
|
||||||
(
|
(
|
||||||
controls: ItemControls | undefined,
|
controls: ItemControls | undefined,
|
||||||
item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
|
item: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined,
|
||||||
internalState: ItemListStateActions | undefined,
|
internalState: ItemListStateActions | undefined,
|
||||||
itemType: LibraryItem,
|
itemType: LibraryItem,
|
||||||
playType: Play,
|
playType: Play,
|
||||||
@@ -108,7 +109,7 @@ const createPlayHandler =
|
|||||||
const createFavoriteHandler =
|
const createFavoriteHandler =
|
||||||
(
|
(
|
||||||
controls: ItemControls | undefined,
|
controls: ItemControls | undefined,
|
||||||
item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
|
item: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined,
|
||||||
internalState: ItemListStateActions | undefined,
|
internalState: ItemListStateActions | undefined,
|
||||||
itemType: LibraryItem,
|
itemType: LibraryItem,
|
||||||
) =>
|
) =>
|
||||||
@@ -133,7 +134,7 @@ const createFavoriteHandler =
|
|||||||
const createRatingChangeHandler =
|
const createRatingChangeHandler =
|
||||||
(
|
(
|
||||||
controls: ItemControls | undefined,
|
controls: ItemControls | undefined,
|
||||||
item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
|
item: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined,
|
||||||
internalState: ItemListStateActions | undefined,
|
internalState: ItemListStateActions | undefined,
|
||||||
itemType: LibraryItem,
|
itemType: LibraryItem,
|
||||||
) =>
|
) =>
|
||||||
@@ -165,7 +166,7 @@ const moreDoubleClickHandler = (e: MouseEvent<HTMLButtonElement>) => {
|
|||||||
const createMoreHandler =
|
const createMoreHandler =
|
||||||
(
|
(
|
||||||
controls: ItemControls | undefined,
|
controls: ItemControls | undefined,
|
||||||
item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
|
item: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined,
|
||||||
internalState: ItemListStateActions | undefined,
|
internalState: ItemListStateActions | undefined,
|
||||||
itemType: LibraryItem,
|
itemType: LibraryItem,
|
||||||
) =>
|
) =>
|
||||||
@@ -183,7 +184,7 @@ const createMoreHandler =
|
|||||||
const createExpandHandler =
|
const createExpandHandler =
|
||||||
(
|
(
|
||||||
controls: ItemControls | undefined,
|
controls: ItemControls | undefined,
|
||||||
item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
|
item: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined,
|
||||||
internalState: ItemListStateActions | undefined,
|
internalState: ItemListStateActions | undefined,
|
||||||
itemType: LibraryItem,
|
itemType: LibraryItem,
|
||||||
) =>
|
) =>
|
||||||
|
|||||||
@@ -113,6 +113,18 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.genre-placeholder {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.genre-placeholder-text {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.detail-container {
|
.detail-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -38,22 +38,26 @@ import {
|
|||||||
Album,
|
Album,
|
||||||
AlbumArtist,
|
AlbumArtist,
|
||||||
Artist,
|
Artist,
|
||||||
|
Genre,
|
||||||
LibraryItem,
|
LibraryItem,
|
||||||
Playlist,
|
Playlist,
|
||||||
Song,
|
Song,
|
||||||
} from '/@/shared/types/domain-types';
|
} from '/@/shared/types/domain-types';
|
||||||
import { DragOperation, DragTarget } from '/@/shared/types/drag-and-drop';
|
import { DragOperation, DragTarget } from '/@/shared/types/drag-and-drop';
|
||||||
|
import { stringToColor } from '/@/shared/utils/string-to-color';
|
||||||
|
|
||||||
export type DataRow = {
|
export type DataRow = {
|
||||||
align?: 'center' | 'end' | 'start';
|
align?: 'center' | 'end' | 'start';
|
||||||
format: (data: Album | AlbumArtist | Artist | Playlist | Song) => null | ReactNode | string;
|
format: (
|
||||||
|
data: Album | AlbumArtist | Artist | Genre | Playlist | Song,
|
||||||
|
) => null | ReactNode | string;
|
||||||
id: string;
|
id: string;
|
||||||
isMuted?: boolean;
|
isMuted?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ItemCardProps {
|
export interface ItemCardProps {
|
||||||
controls?: ItemControls;
|
controls?: ItemControls;
|
||||||
data: Album | AlbumArtist | Artist | Playlist | Song | undefined;
|
data: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined;
|
||||||
enableDrag?: boolean;
|
enableDrag?: boolean;
|
||||||
enableExpansion?: boolean;
|
enableExpansion?: boolean;
|
||||||
enableMultiSelect?: boolean;
|
enableMultiSelect?: boolean;
|
||||||
@@ -341,16 +345,28 @@ const CompactItemCard = ({
|
|||||||
|
|
||||||
const imageContainerContent = (
|
const imageContainerContent = (
|
||||||
<>
|
<>
|
||||||
<ItemImage
|
{itemType === LibraryItem.GENRE &&
|
||||||
className={clsx(styles.image, {
|
data &&
|
||||||
[styles.isRound]: isRound,
|
'name' in data &&
|
||||||
})}
|
typeof (data as Genre).name === 'string' ? (
|
||||||
enableDebounce={false}
|
<GenreImagePlaceholder
|
||||||
id={data?.imageId}
|
className={clsx(styles.image, styles.genrePlaceholder, {
|
||||||
itemType={itemType}
|
[styles.isRound]: isRound,
|
||||||
src={(data as Album | AlbumArtist | Playlist | Song)?.imageUrl}
|
})}
|
||||||
type="itemCard"
|
name={(data as Genre).name}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<ItemImage
|
||||||
|
className={clsx(styles.image, {
|
||||||
|
[styles.isRound]: isRound,
|
||||||
|
})}
|
||||||
|
enableDebounce={false}
|
||||||
|
id={data?.imageId}
|
||||||
|
itemType={itemType}
|
||||||
|
src={(data as Album | AlbumArtist | Playlist | Song)?.imageUrl}
|
||||||
|
type="itemCard"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{isFavorite && <div className={styles.favoriteBadge} />}
|
{isFavorite && <div className={styles.favoriteBadge} />}
|
||||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
@@ -565,14 +581,26 @@ const DefaultItemCard = ({
|
|||||||
|
|
||||||
const imageContainerContent = (
|
const imageContainerContent = (
|
||||||
<>
|
<>
|
||||||
<ItemImage
|
{itemType === LibraryItem.GENRE &&
|
||||||
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
data &&
|
||||||
enableDebounce={false}
|
'name' in data &&
|
||||||
id={data?.imageId}
|
typeof (data as Genre).name === 'string' ? (
|
||||||
itemType={itemType}
|
<GenreImagePlaceholder
|
||||||
src={(data as Album | AlbumArtist | Playlist | Song)?.imageUrl}
|
className={clsx(styles.image, styles.genrePlaceholder, {
|
||||||
type="itemCard"
|
[styles.isRound]: isRound,
|
||||||
/>
|
})}
|
||||||
|
name={(data as Genre).name}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ItemImage
|
||||||
|
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
||||||
|
enableDebounce={false}
|
||||||
|
id={data?.imageId}
|
||||||
|
itemType={itemType}
|
||||||
|
src={(data as Album | AlbumArtist | Playlist | Song)?.imageUrl}
|
||||||
|
type="itemCard"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{isFavorite && <div className={styles.favoriteBadge} />}
|
{isFavorite && <div className={styles.favoriteBadge} />}
|
||||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
@@ -850,14 +878,26 @@ const PosterItemCard = ({
|
|||||||
|
|
||||||
const imageContainerContent = (
|
const imageContainerContent = (
|
||||||
<>
|
<>
|
||||||
<ItemImage
|
{itemType === LibraryItem.GENRE &&
|
||||||
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
data &&
|
||||||
enableDebounce={false}
|
'name' in data &&
|
||||||
id={(data as { imageId: string })?.imageId}
|
typeof (data as Genre).name === 'string' ? (
|
||||||
itemType={itemType}
|
<GenreImagePlaceholder
|
||||||
src={(data as { imageUrl: string })?.imageUrl}
|
className={clsx(styles.image, styles.genrePlaceholder, {
|
||||||
type="itemCard"
|
[styles.isRound]: isRound,
|
||||||
/>
|
})}
|
||||||
|
name={(data as Genre).name}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ItemImage
|
||||||
|
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
||||||
|
enableDebounce={false}
|
||||||
|
id={(data as { imageId: string })?.imageId}
|
||||||
|
itemType={itemType}
|
||||||
|
src={(data as { imageUrl: string })?.imageUrl}
|
||||||
|
type="itemCard"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{isFavorite && <div className={styles.favoriteBadge} />}
|
{isFavorite && <div className={styles.favoriteBadge} />}
|
||||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
@@ -999,6 +1039,17 @@ export const getDataRows = (type?: 'compact' | 'default' | 'poster'): DataRow[]
|
|||||||
{data.name}
|
{data.name}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
case LibraryItem.GENRE:
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
state={{ item: data }}
|
||||||
|
to={generatePath(AppRoute.LIBRARY_GENRES_DETAIL, {
|
||||||
|
genreId: data.id,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{data.name}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
case LibraryItem.PLAYLIST:
|
case LibraryItem.PLAYLIST:
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
@@ -1227,7 +1278,7 @@ export const getDataRowsCount = () => {
|
|||||||
return getDataRows().length;
|
return getDataRows().length;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getImageUrl = (data: Album | AlbumArtist | Artist | Playlist | Song | undefined) => {
|
const getImageUrl = (data: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined) => {
|
||||||
if (data && 'imageUrl' in data) {
|
if (data && 'imageUrl' in data) {
|
||||||
return data.imageUrl || undefined;
|
return data.imageUrl || undefined;
|
||||||
}
|
}
|
||||||
@@ -1235,8 +1286,30 @@ const getImageUrl = (data: Album | AlbumArtist | Artist | Playlist | Song | unde
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const GenreImagePlaceholder = ({ className, name }: { className?: string; name: string }) => {
|
||||||
|
const { color, isLight } = useMemo(() => stringToColor(name), [name]);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={className}
|
||||||
|
style={{
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: color,
|
||||||
|
color: isLight ? '#000' : '#fff',
|
||||||
|
display: 'flex',
|
||||||
|
height: '100%',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: 'var(--theme-spacing-sm)',
|
||||||
|
textAlign: 'center',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className={styles.genrePlaceholderText}>{name}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const getItemNavigationPath = (
|
const getItemNavigationPath = (
|
||||||
data: Album | AlbumArtist | Artist | Playlist | Song | undefined,
|
data: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined,
|
||||||
itemType: LibraryItem,
|
itemType: LibraryItem,
|
||||||
): null | string => {
|
): null | string => {
|
||||||
if (!data || !('id' in data) || !data.id) {
|
if (!data || !('id' in data) || !data.id) {
|
||||||
@@ -1255,7 +1328,7 @@ const ItemCardRow = memo(
|
|||||||
row,
|
row,
|
||||||
type,
|
type,
|
||||||
}: {
|
}: {
|
||||||
data: Album | AlbumArtist | Artist | Playlist | Song | undefined;
|
data: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined;
|
||||||
index: number;
|
index: number;
|
||||||
row: DataRow;
|
row: DataRow;
|
||||||
type?: 'compact' | 'default' | 'poster';
|
type?: 'compact' | 'default' | 'poster';
|
||||||
|
|||||||
@@ -2,7 +2,15 @@ import {
|
|||||||
ItemListStateActions,
|
ItemListStateActions,
|
||||||
ItemListStateItemWithRequiredProperties,
|
ItemListStateItemWithRequiredProperties,
|
||||||
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { Album, AlbumArtist, Artist, Folder, Playlist, Song } from '/@/shared/types/domain-types';
|
import {
|
||||||
|
Album,
|
||||||
|
AlbumArtist,
|
||||||
|
Artist,
|
||||||
|
Folder,
|
||||||
|
Genre,
|
||||||
|
Playlist,
|
||||||
|
Song,
|
||||||
|
} from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type guard to assert that an item has the required properties for dragging
|
* Type guard to assert that an item has the required properties for dragging
|
||||||
@@ -34,7 +42,7 @@ const hasRequiredDragProperties = (
|
|||||||
* @returns Array of items that should be dragged (with original values, asserting id, itemType, and _serverId)
|
* @returns Array of items that should be dragged (with original values, asserting id, itemType, and _serverId)
|
||||||
*/
|
*/
|
||||||
export const getDraggedItems = (
|
export const getDraggedItems = (
|
||||||
data: Album | AlbumArtist | Artist | Folder | Playlist | Song | undefined,
|
data: Album | AlbumArtist | Artist | Folder | Genre | Playlist | Song | undefined,
|
||||||
internalState?: ItemListStateActions,
|
internalState?: ItemListStateActions,
|
||||||
updateSelection: boolean = true,
|
updateSelection: boolean = true,
|
||||||
): ItemListStateItemWithRequiredProperties[] => {
|
): ItemListStateItemWithRequiredProperties[] => {
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ const getDefaultRowsForItemType = (
|
|||||||
return [rowMap.get('name')].filter(
|
return [rowMap.get('name')].filter(
|
||||||
(row): row is NonNullable<typeof row> => row !== undefined,
|
(row): row is NonNullable<typeof row> => row !== undefined,
|
||||||
);
|
);
|
||||||
|
case LibraryItem.GENRE:
|
||||||
|
return [rowMap.get('name')].filter(
|
||||||
|
(row): row is NonNullable<typeof row> => row !== undefined,
|
||||||
|
);
|
||||||
case LibraryItem.PLAYLIST:
|
case LibraryItem.PLAYLIST:
|
||||||
return [rowMap.get('name')].filter(
|
return [rowMap.get('name')].filter(
|
||||||
(row): row is NonNullable<typeof row> => row !== undefined,
|
(row): row is NonNullable<typeof row> => row !== undefined,
|
||||||
@@ -53,6 +57,7 @@ const getRowIdFromTableColumn = (tableColumn: TableColumn): null | string => {
|
|||||||
[TableColumn.CHANNELS]: null,
|
[TableColumn.CHANNELS]: null,
|
||||||
[TableColumn.CODEC]: null,
|
[TableColumn.CODEC]: null,
|
||||||
[TableColumn.COMMENT]: null,
|
[TableColumn.COMMENT]: null,
|
||||||
|
[TableColumn.COMPOSER]: null,
|
||||||
[TableColumn.DATE_ADDED]: 'createdAt',
|
[TableColumn.DATE_ADDED]: 'createdAt',
|
||||||
[TableColumn.DISC_NUMBER]: null,
|
[TableColumn.DISC_NUMBER]: null,
|
||||||
[TableColumn.DURATION]: 'duration',
|
[TableColumn.DURATION]: 'duration',
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
AlbumArtist,
|
AlbumArtist,
|
||||||
Artist,
|
Artist,
|
||||||
Folder,
|
Folder,
|
||||||
|
Genre,
|
||||||
LibraryItem,
|
LibraryItem,
|
||||||
Playlist,
|
Playlist,
|
||||||
Song,
|
Song,
|
||||||
@@ -78,7 +79,15 @@ export interface ItemListHandle {
|
|||||||
scrollToOffset: (offset: number, options?: { behavior?: 'auto' | 'smooth' }) => void;
|
scrollToOffset: (offset: number, options?: { behavior?: 'auto' | 'smooth' }) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ItemListItem = Album | AlbumArtist | Artist | Folder | Playlist | Song | undefined;
|
export type ItemListItem =
|
||||||
|
| Album
|
||||||
|
| AlbumArtist
|
||||||
|
| Artist
|
||||||
|
| Folder
|
||||||
|
| Genre
|
||||||
|
| Playlist
|
||||||
|
| Song
|
||||||
|
| undefined;
|
||||||
|
|
||||||
export interface ItemListTableComponentProps<TQuery> extends ItemListComponentProps<TQuery> {
|
export interface ItemListTableComponentProps<TQuery> extends ItemListComponentProps<TQuery> {
|
||||||
autoFitColumns?: boolean;
|
autoFitColumns?: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user