mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-01 02:13:33 +00:00
Lint all files
This commit is contained in:
parent
22af76b4d6
commit
30e52ebb54
334 changed files with 76519 additions and 75932 deletions
|
|
@ -11,208 +11,208 @@ import { Album, AlbumArtist, Artist, LibraryItem } from '/@/renderer/api/types';
|
|||
import { CardRows } from '/@/renderer/components/card/card-rows';
|
||||
|
||||
const CardWrapper = styled.div<{
|
||||
link?: boolean;
|
||||
link?: boolean;
|
||||
}>`
|
||||
padding: 1rem;
|
||||
background: var(--card-default-bg);
|
||||
border-radius: var(--card-default-radius);
|
||||
cursor: ${({ link }) => link && 'pointer'};
|
||||
transition: border 0.2s ease-in-out, background 0.2s ease-in-out;
|
||||
padding: 1rem;
|
||||
background: var(--card-default-bg);
|
||||
border-radius: var(--card-default-radius);
|
||||
cursor: ${({ link }) => link && 'pointer'};
|
||||
transition: border 0.2s ease-in-out, background 0.2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background: var(--card-default-bg-hover);
|
||||
}
|
||||
|
||||
&:hover div {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:hover * {
|
||||
&::before {
|
||||
opacity: 0.5;
|
||||
&:hover {
|
||||
background: var(--card-default-bg-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 1px solid #fff;
|
||||
}
|
||||
&:hover div {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:hover * {
|
||||
&::before {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 1px solid #fff;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledCard = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
border-radius: var(--card-default-radius);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
border-radius: var(--card-default-radius);
|
||||
`;
|
||||
|
||||
const ImageSection = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border-radius: var(--card-default-radius);
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border-radius: var(--card-default-radius);
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 100%) 35%, rgba(0, 0, 0, 0%) 100%);
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease-in-out;
|
||||
content: '';
|
||||
user-select: none;
|
||||
}
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 100%) 35%, rgba(0, 0, 0, 0%) 100%);
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease-in-out;
|
||||
content: '';
|
||||
user-select: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const Image = styled(SimpleImg)`
|
||||
border-radius: var(--card-default-radius);
|
||||
box-shadow: 2px 2px 10px 2px rgba(0, 0, 0, 20%);
|
||||
border-radius: var(--card-default-radius);
|
||||
box-shadow: 2px 2px 10px 2px rgba(0, 0, 0, 20%);
|
||||
`;
|
||||
|
||||
const ControlsContainer = styled.div`
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 50;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease-in-out;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 50;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease-in-out;
|
||||
`;
|
||||
|
||||
const DetailSection = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const Row = styled.div<{ $secondary?: boolean }>`
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 22px;
|
||||
padding: 0 0.2rem;
|
||||
overflow: hidden;
|
||||
color: ${({ $secondary }) => ($secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)')};
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 22px;
|
||||
padding: 0 0.2rem;
|
||||
overflow: hidden;
|
||||
color: ${({ $secondary }) => ($secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)')};
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
interface BaseGridCardProps {
|
||||
controls: {
|
||||
cardRows: CardRow<Album | Artist | AlbumArtist>[];
|
||||
itemType: LibraryItem;
|
||||
playButtonBehavior: Play;
|
||||
route: CardRoute;
|
||||
};
|
||||
data: any;
|
||||
handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void;
|
||||
loading?: boolean;
|
||||
size: number;
|
||||
controls: {
|
||||
cardRows: CardRow<Album | Artist | AlbumArtist>[];
|
||||
itemType: LibraryItem;
|
||||
playButtonBehavior: Play;
|
||||
route: CardRoute;
|
||||
};
|
||||
data: any;
|
||||
handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void;
|
||||
loading?: boolean;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export const AlbumCard = ({
|
||||
loading,
|
||||
size,
|
||||
handlePlayQueueAdd,
|
||||
data,
|
||||
controls,
|
||||
loading,
|
||||
size,
|
||||
handlePlayQueueAdd,
|
||||
data,
|
||||
controls,
|
||||
}: BaseGridCardProps) => {
|
||||
const navigate = useNavigate();
|
||||
const { itemType, cardRows, route } = controls;
|
||||
const navigate = useNavigate();
|
||||
const { itemType, cardRows, route } = controls;
|
||||
|
||||
const handleNavigate = useCallback(() => {
|
||||
navigate(
|
||||
generatePath(
|
||||
route.route,
|
||||
route.slugs?.reduce((acc, slug) => {
|
||||
return {
|
||||
...acc,
|
||||
[slug.slugProperty]: data[slug.idProperty],
|
||||
};
|
||||
}, {}),
|
||||
),
|
||||
);
|
||||
}, [data, navigate, route.route, route.slugs]);
|
||||
const handleNavigate = useCallback(() => {
|
||||
navigate(
|
||||
generatePath(
|
||||
route.route,
|
||||
route.slugs?.reduce((acc, slug) => {
|
||||
return {
|
||||
...acc,
|
||||
[slug.slugProperty]: data[slug.idProperty],
|
||||
};
|
||||
}, {}),
|
||||
),
|
||||
);
|
||||
}, [data, navigate, route.route, route.slugs]);
|
||||
|
||||
if (!loading) {
|
||||
return (
|
||||
<CardWrapper
|
||||
link
|
||||
onClick={handleNavigate}
|
||||
>
|
||||
<StyledCard>
|
||||
<ImageSection>
|
||||
{data?.imageUrl ? (
|
||||
<Image
|
||||
animationDuration={0.3}
|
||||
height={size}
|
||||
imgStyle={{ objectFit: 'cover' }}
|
||||
placeholder={data?.imagePlaceholderUrl || 'var(--card-default-bg)'}
|
||||
src={data?.imageUrl}
|
||||
width={size}
|
||||
/>
|
||||
) : (
|
||||
<Center
|
||||
sx={{
|
||||
background: 'var(--placeholder-bg)',
|
||||
borderRadius: 'var(--card-default-radius)',
|
||||
height: `${size}px`,
|
||||
width: `${size}px`,
|
||||
}}
|
||||
>
|
||||
<RiAlbumFill
|
||||
color="var(--placeholder-fg)"
|
||||
size={35}
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
<ControlsContainer>
|
||||
<CardControls
|
||||
handlePlayQueueAdd={handlePlayQueueAdd}
|
||||
itemData={data}
|
||||
itemType={itemType}
|
||||
/>
|
||||
</ControlsContainer>
|
||||
</ImageSection>
|
||||
<DetailSection>
|
||||
<CardRows
|
||||
data={data}
|
||||
rows={cardRows}
|
||||
/>
|
||||
</DetailSection>
|
||||
</StyledCard>
|
||||
</CardWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CardWrapper>
|
||||
<StyledCard style={{ alignItems: 'center', display: 'flex' }}>
|
||||
<Skeleton
|
||||
visible
|
||||
height={size}
|
||||
radius="sm"
|
||||
width={size}
|
||||
>
|
||||
<ImageSection />
|
||||
</Skeleton>
|
||||
<DetailSection style={{ width: '100%' }}>
|
||||
{cardRows.map((_row: CardRow<Album>, index: number) => (
|
||||
<Skeleton
|
||||
visible
|
||||
height={15}
|
||||
my={3}
|
||||
radius="md"
|
||||
width={!data ? (index > 0 ? '50%' : '90%') : '100%'}
|
||||
if (!loading) {
|
||||
return (
|
||||
<CardWrapper
|
||||
link
|
||||
onClick={handleNavigate}
|
||||
>
|
||||
<Row />
|
||||
</Skeleton>
|
||||
))}
|
||||
</DetailSection>
|
||||
</StyledCard>
|
||||
</CardWrapper>
|
||||
);
|
||||
<StyledCard>
|
||||
<ImageSection>
|
||||
{data?.imageUrl ? (
|
||||
<Image
|
||||
animationDuration={0.3}
|
||||
height={size}
|
||||
imgStyle={{ objectFit: 'cover' }}
|
||||
placeholder={data?.imagePlaceholderUrl || 'var(--card-default-bg)'}
|
||||
src={data?.imageUrl}
|
||||
width={size}
|
||||
/>
|
||||
) : (
|
||||
<Center
|
||||
sx={{
|
||||
background: 'var(--placeholder-bg)',
|
||||
borderRadius: 'var(--card-default-radius)',
|
||||
height: `${size}px`,
|
||||
width: `${size}px`,
|
||||
}}
|
||||
>
|
||||
<RiAlbumFill
|
||||
color="var(--placeholder-fg)"
|
||||
size={35}
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
<ControlsContainer>
|
||||
<CardControls
|
||||
handlePlayQueueAdd={handlePlayQueueAdd}
|
||||
itemData={data}
|
||||
itemType={itemType}
|
||||
/>
|
||||
</ControlsContainer>
|
||||
</ImageSection>
|
||||
<DetailSection>
|
||||
<CardRows
|
||||
data={data}
|
||||
rows={cardRows}
|
||||
/>
|
||||
</DetailSection>
|
||||
</StyledCard>
|
||||
</CardWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CardWrapper>
|
||||
<StyledCard style={{ alignItems: 'center', display: 'flex' }}>
|
||||
<Skeleton
|
||||
visible
|
||||
height={size}
|
||||
radius="sm"
|
||||
width={size}
|
||||
>
|
||||
<ImageSection />
|
||||
</Skeleton>
|
||||
<DetailSection style={{ width: '100%' }}>
|
||||
{cardRows.map((_row: CardRow<Album>, index: number) => (
|
||||
<Skeleton
|
||||
visible
|
||||
height={15}
|
||||
my={3}
|
||||
radius="md"
|
||||
width={!data ? (index > 0 ? '50%' : '90%') : '100%'}
|
||||
>
|
||||
<Row />
|
||||
</Skeleton>
|
||||
))}
|
||||
</DetailSection>
|
||||
</StyledCard>
|
||||
</CardWrapper>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,69 +11,69 @@ import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
|||
import { LibraryItem } from '/@/renderer/api/types';
|
||||
import { useHandleGeneralContextMenu } from '/@/renderer/features/context-menu/hooks/use-handle-context-menu';
|
||||
import {
|
||||
ALBUM_CONTEXT_MENU_ITEMS,
|
||||
ARTIST_CONTEXT_MENU_ITEMS,
|
||||
ALBUM_CONTEXT_MENU_ITEMS,
|
||||
ARTIST_CONTEXT_MENU_ITEMS,
|
||||
} from '/@/renderer/features/context-menu/context-menu-items';
|
||||
|
||||
type PlayButtonType = UnstyledButtonProps & React.ComponentPropsWithoutRef<'button'>;
|
||||
|
||||
const PlayButton = styled.button<PlayButtonType>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
transition: scale 0.2s linear;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
transition: scale 0.2s linear;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
scale: 1.1;
|
||||
}
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
scale: 1.1;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 1;
|
||||
scale: 1;
|
||||
}
|
||||
&:active {
|
||||
opacity: 1;
|
||||
scale: 1;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: rgb(0, 0, 0);
|
||||
stroke: rgb(0, 0, 0);
|
||||
}
|
||||
svg {
|
||||
fill: rgb(0, 0, 0);
|
||||
stroke: rgb(0, 0, 0);
|
||||
}
|
||||
`;
|
||||
|
||||
const SecondaryButton = styled(_Button)`
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
transition: scale 0.2s linear;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
transition: scale 0.2s linear;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
scale: 1.1;
|
||||
}
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
scale: 1.1;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 1;
|
||||
scale: 1;
|
||||
}
|
||||
&:active {
|
||||
opacity: 1;
|
||||
scale: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const GridCardControlsContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const ControlsRow = styled.div`
|
||||
width: 100%;
|
||||
height: calc(100% / 3);
|
||||
width: 100%;
|
||||
height: calc(100% / 3);
|
||||
`;
|
||||
|
||||
// const TopControls = styled(ControlsRow)`
|
||||
|
|
@ -91,87 +91,87 @@ const ControlsRow = styled.div`
|
|||
// `;
|
||||
|
||||
const BottomControls = styled(ControlsRow)`
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
padding: 1rem 0.5rem;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
padding: 1rem 0.5rem;
|
||||
`;
|
||||
|
||||
const FavoriteWrapper = styled.span<{ isFavorite: boolean }>`
|
||||
svg {
|
||||
fill: ${(props) => props.isFavorite && 'var(--primary-color)'};
|
||||
}
|
||||
svg {
|
||||
fill: ${(props) => props.isFavorite && 'var(--primary-color)'};
|
||||
}
|
||||
`;
|
||||
|
||||
export const CardControls = ({
|
||||
itemData,
|
||||
itemType,
|
||||
handlePlayQueueAdd,
|
||||
}: {
|
||||
handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void;
|
||||
itemData: any;
|
||||
itemType: LibraryItem;
|
||||
}) => {
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
|
||||
const handlePlay = (e: MouseEvent<HTMLButtonElement>, playType?: Play) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handlePlayQueueAdd?.({
|
||||
byItemType: {
|
||||
id: [itemData.id],
|
||||
type: itemType,
|
||||
},
|
||||
playType: playType || playButtonBehavior,
|
||||
});
|
||||
};
|
||||
|
||||
const handleContextMenu = useHandleGeneralContextMenu(
|
||||
itemData,
|
||||
itemType,
|
||||
itemType === LibraryItem.ALBUM ? ALBUM_CONTEXT_MENU_ITEMS : ARTIST_CONTEXT_MENU_ITEMS,
|
||||
);
|
||||
handlePlayQueueAdd,
|
||||
}: {
|
||||
handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void;
|
||||
itemData: any;
|
||||
itemType: LibraryItem;
|
||||
}) => {
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
|
||||
return (
|
||||
<GridCardControlsContainer>
|
||||
<BottomControls>
|
||||
<PlayButton onClick={handlePlay}>
|
||||
<RiPlayFill size={25} />
|
||||
</PlayButton>
|
||||
<Group spacing="xs">
|
||||
<SecondaryButton
|
||||
disabled
|
||||
p={5}
|
||||
sx={{ svg: { fill: 'white !important' } }}
|
||||
variant="subtle"
|
||||
>
|
||||
<FavoriteWrapper isFavorite={itemData?.isFavorite}>
|
||||
{itemData?.isFavorite ? (
|
||||
<RiHeartFill size={20} />
|
||||
) : (
|
||||
<RiHeartLine
|
||||
color="white"
|
||||
size={20}
|
||||
/>
|
||||
)}
|
||||
</FavoriteWrapper>
|
||||
</SecondaryButton>
|
||||
<SecondaryButton
|
||||
p={5}
|
||||
sx={{ svg: { fill: 'white !important' } }}
|
||||
variant="subtle"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleContextMenu(e, [itemData]);
|
||||
}}
|
||||
>
|
||||
<RiMore2Fill
|
||||
color="white"
|
||||
size={20}
|
||||
/>
|
||||
</SecondaryButton>
|
||||
</Group>
|
||||
</BottomControls>
|
||||
</GridCardControlsContainer>
|
||||
);
|
||||
const handlePlay = (e: MouseEvent<HTMLButtonElement>, playType?: Play) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handlePlayQueueAdd?.({
|
||||
byItemType: {
|
||||
id: [itemData.id],
|
||||
type: itemType,
|
||||
},
|
||||
playType: playType || playButtonBehavior,
|
||||
});
|
||||
};
|
||||
|
||||
const handleContextMenu = useHandleGeneralContextMenu(
|
||||
itemType,
|
||||
itemType === LibraryItem.ALBUM ? ALBUM_CONTEXT_MENU_ITEMS : ARTIST_CONTEXT_MENU_ITEMS,
|
||||
);
|
||||
|
||||
return (
|
||||
<GridCardControlsContainer>
|
||||
<BottomControls>
|
||||
<PlayButton onClick={handlePlay}>
|
||||
<RiPlayFill size={25} />
|
||||
</PlayButton>
|
||||
<Group spacing="xs">
|
||||
<SecondaryButton
|
||||
disabled
|
||||
p={5}
|
||||
sx={{ svg: { fill: 'white !important' } }}
|
||||
variant="subtle"
|
||||
>
|
||||
<FavoriteWrapper isFavorite={itemData?.isFavorite}>
|
||||
{itemData?.isFavorite ? (
|
||||
<RiHeartFill size={20} />
|
||||
) : (
|
||||
<RiHeartLine
|
||||
color="white"
|
||||
size={20}
|
||||
/>
|
||||
)}
|
||||
</FavoriteWrapper>
|
||||
</SecondaryButton>
|
||||
<SecondaryButton
|
||||
p={5}
|
||||
sx={{ svg: { fill: 'white !important' } }}
|
||||
variant="subtle"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleContextMenu(e, [itemData]);
|
||||
}}
|
||||
>
|
||||
<RiMore2Fill
|
||||
color="white"
|
||||
size={20}
|
||||
/>
|
||||
</SecondaryButton>
|
||||
</Group>
|
||||
</BottomControls>
|
||||
</GridCardControlsContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,205 +8,208 @@ import { AppRoute } from '/@/renderer/router/routes';
|
|||
import { CardRow } from '/@/renderer/types';
|
||||
|
||||
const Row = styled.div<{ $secondary?: boolean }>`
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 22px;
|
||||
padding: 0 0.2rem;
|
||||
overflow: hidden;
|
||||
color: ${({ $secondary }) => ($secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)')};
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 22px;
|
||||
padding: 0 0.2rem;
|
||||
overflow: hidden;
|
||||
color: ${({ $secondary }) => ($secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)')};
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
interface CardRowsProps {
|
||||
data: any;
|
||||
rows: CardRow<Album>[] | CardRow<Artist>[] | CardRow<AlbumArtist>[];
|
||||
data: any;
|
||||
rows: CardRow<Album>[] | CardRow<Artist>[] | CardRow<AlbumArtist>[];
|
||||
}
|
||||
|
||||
export const CardRows = ({ data, rows }: CardRowsProps) => {
|
||||
return (
|
||||
<>
|
||||
{rows.map((row, index: number) => {
|
||||
if (row.arrayProperty && row.route) {
|
||||
return (
|
||||
<Row
|
||||
key={`row-${row.property}-${index}`}
|
||||
$secondary={index > 0}
|
||||
>
|
||||
{data[row.property].map((item: any, itemIndex: number) => (
|
||||
<React.Fragment key={`${data.id}-${item.id}`}>
|
||||
{itemIndex > 0 && (
|
||||
<Text
|
||||
$noSelect
|
||||
$secondary
|
||||
sx={{
|
||||
display: 'inline-block',
|
||||
padding: '0 2px 0 1px',
|
||||
}}
|
||||
>
|
||||
,
|
||||
</Text>
|
||||
)}{' '}
|
||||
<Text
|
||||
$link
|
||||
$noSelect
|
||||
$secondary={index > 0}
|
||||
component={Link}
|
||||
overflow="hidden"
|
||||
size={index > 0 ? 'sm' : 'md'}
|
||||
to={generatePath(
|
||||
row.route!.route,
|
||||
row.route!.slugs?.reduce((acc, slug) => {
|
||||
return {
|
||||
...acc,
|
||||
[slug.slugProperty]: data[row.property][itemIndex][slug.idProperty],
|
||||
};
|
||||
}, {}),
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{row.arrayProperty && item[row.arrayProperty]}
|
||||
</Text>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{rows.map((row, index: number) => {
|
||||
if (row.arrayProperty && row.route) {
|
||||
return (
|
||||
<Row
|
||||
key={`row-${row.property}-${index}`}
|
||||
$secondary={index > 0}
|
||||
>
|
||||
{data[row.property].map((item: any, itemIndex: number) => (
|
||||
<React.Fragment key={`${data.id}-${item.id}`}>
|
||||
{itemIndex > 0 && (
|
||||
<Text
|
||||
$noSelect
|
||||
$secondary
|
||||
sx={{
|
||||
display: 'inline-block',
|
||||
padding: '0 2px 0 1px',
|
||||
}}
|
||||
>
|
||||
,
|
||||
</Text>
|
||||
)}{' '}
|
||||
<Text
|
||||
$link
|
||||
$noSelect
|
||||
$secondary={index > 0}
|
||||
component={Link}
|
||||
overflow="hidden"
|
||||
size={index > 0 ? 'sm' : 'md'}
|
||||
to={generatePath(
|
||||
row.route!.route,
|
||||
row.route!.slugs?.reduce((acc, slug) => {
|
||||
return {
|
||||
...acc,
|
||||
[slug.slugProperty]:
|
||||
data[row.property][itemIndex][
|
||||
slug.idProperty
|
||||
],
|
||||
};
|
||||
}, {}),
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{row.arrayProperty && item[row.arrayProperty]}
|
||||
</Text>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
if (row.arrayProperty) {
|
||||
return (
|
||||
<Row key={`row-${row.property}`}>
|
||||
{data[row.property].map((item: any) => (
|
||||
<Text
|
||||
key={`${data.id}-${item.id}`}
|
||||
$noSelect
|
||||
$secondary={index > 0}
|
||||
overflow="hidden"
|
||||
size={index > 0 ? 'sm' : 'md'}
|
||||
>
|
||||
{row.arrayProperty && item[row.arrayProperty]}
|
||||
</Text>
|
||||
))}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
if (row.arrayProperty) {
|
||||
return (
|
||||
<Row key={`row-${row.property}`}>
|
||||
{data[row.property].map((item: any) => (
|
||||
<Text
|
||||
key={`${data.id}-${item.id}`}
|
||||
$noSelect
|
||||
$secondary={index > 0}
|
||||
overflow="hidden"
|
||||
size={index > 0 ? 'sm' : 'md'}
|
||||
>
|
||||
{row.arrayProperty && item[row.arrayProperty]}
|
||||
</Text>
|
||||
))}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Row key={`row-${row.property}`}>
|
||||
{row.route ? (
|
||||
<Text
|
||||
$link
|
||||
$noSelect
|
||||
component={Link}
|
||||
overflow="hidden"
|
||||
to={generatePath(
|
||||
row.route.route,
|
||||
row.route.slugs?.reduce((acc, slug) => {
|
||||
return {
|
||||
...acc,
|
||||
[slug.slugProperty]: data[slug.idProperty],
|
||||
};
|
||||
}, {}),
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{data && data[row.property]}
|
||||
</Text>
|
||||
) : (
|
||||
<Text
|
||||
$noSelect
|
||||
$secondary={index > 0}
|
||||
overflow="hidden"
|
||||
size={index > 0 ? 'sm' : 'md'}
|
||||
>
|
||||
{data && data[row.property]}
|
||||
</Text>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<Row key={`row-${row.property}`}>
|
||||
{row.route ? (
|
||||
<Text
|
||||
$link
|
||||
$noSelect
|
||||
component={Link}
|
||||
overflow="hidden"
|
||||
to={generatePath(
|
||||
row.route.route,
|
||||
row.route.slugs?.reduce((acc, slug) => {
|
||||
return {
|
||||
...acc,
|
||||
[slug.slugProperty]: data[slug.idProperty],
|
||||
};
|
||||
}, {}),
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{data && data[row.property]}
|
||||
</Text>
|
||||
) : (
|
||||
<Text
|
||||
$noSelect
|
||||
$secondary={index > 0}
|
||||
overflow="hidden"
|
||||
size={index > 0 ? 'sm' : 'md'}
|
||||
>
|
||||
{data && data[row.property]}
|
||||
</Text>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ALBUM_CARD_ROWS: { [key: string]: CardRow<Album> } = {
|
||||
albumArtists: {
|
||||
arrayProperty: 'name',
|
||||
property: 'albumArtists',
|
||||
route: {
|
||||
route: AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
|
||||
albumArtists: {
|
||||
arrayProperty: 'name',
|
||||
property: 'albumArtists',
|
||||
route: {
|
||||
route: AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
artists: {
|
||||
arrayProperty: 'name',
|
||||
property: 'artists',
|
||||
route: {
|
||||
route: AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
|
||||
artists: {
|
||||
arrayProperty: 'name',
|
||||
property: 'artists',
|
||||
route: {
|
||||
route: AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
createdAt: {
|
||||
property: 'createdAt',
|
||||
},
|
||||
duration: {
|
||||
property: 'duration',
|
||||
},
|
||||
lastPlayedAt: {
|
||||
property: 'lastPlayedAt',
|
||||
},
|
||||
name: {
|
||||
property: 'name',
|
||||
route: {
|
||||
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'albumId' }],
|
||||
createdAt: {
|
||||
property: 'createdAt',
|
||||
},
|
||||
duration: {
|
||||
property: 'duration',
|
||||
},
|
||||
lastPlayedAt: {
|
||||
property: 'lastPlayedAt',
|
||||
},
|
||||
name: {
|
||||
property: 'name',
|
||||
route: {
|
||||
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'albumId' }],
|
||||
},
|
||||
},
|
||||
playCount: {
|
||||
property: 'playCount',
|
||||
},
|
||||
rating: {
|
||||
property: 'userRating',
|
||||
},
|
||||
releaseDate: {
|
||||
property: 'releaseDate',
|
||||
},
|
||||
releaseYear: {
|
||||
property: 'releaseYear',
|
||||
},
|
||||
songCount: {
|
||||
property: 'songCount',
|
||||
},
|
||||
},
|
||||
playCount: {
|
||||
property: 'playCount',
|
||||
},
|
||||
rating: {
|
||||
property: 'userRating',
|
||||
},
|
||||
releaseDate: {
|
||||
property: 'releaseDate',
|
||||
},
|
||||
releaseYear: {
|
||||
property: 'releaseYear',
|
||||
},
|
||||
songCount: {
|
||||
property: 'songCount',
|
||||
},
|
||||
};
|
||||
|
||||
export const ALBUMARTIST_CARD_ROWS: { [key: string]: CardRow<AlbumArtist> } = {
|
||||
albumCount: {
|
||||
property: 'albumCount',
|
||||
},
|
||||
duration: {
|
||||
property: 'duration',
|
||||
},
|
||||
genres: {
|
||||
property: 'genres',
|
||||
},
|
||||
lastPlayedAt: {
|
||||
property: 'lastPlayedAt',
|
||||
},
|
||||
name: {
|
||||
property: 'name',
|
||||
route: {
|
||||
route: AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
|
||||
albumCount: {
|
||||
property: 'albumCount',
|
||||
},
|
||||
duration: {
|
||||
property: 'duration',
|
||||
},
|
||||
genres: {
|
||||
property: 'genres',
|
||||
},
|
||||
lastPlayedAt: {
|
||||
property: 'lastPlayedAt',
|
||||
},
|
||||
name: {
|
||||
property: 'name',
|
||||
route: {
|
||||
route: AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
|
||||
},
|
||||
},
|
||||
playCount: {
|
||||
property: 'playCount',
|
||||
},
|
||||
rating: {
|
||||
property: 'userRating',
|
||||
},
|
||||
songCount: {
|
||||
property: 'songCount',
|
||||
},
|
||||
},
|
||||
playCount: {
|
||||
property: 'playCount',
|
||||
},
|
||||
rating: {
|
||||
property: 'userRating',
|
||||
},
|
||||
songCount: {
|
||||
property: 'songCount',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,197 +10,197 @@ import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/
|
|||
import { CardRow, PlayQueueAddOptions, Play, CardRoute } from '/@/renderer/types';
|
||||
|
||||
interface BaseGridCardProps {
|
||||
controls: {
|
||||
cardRows: CardRow<Album>[] | CardRow<Artist>[] | CardRow<AlbumArtist>[];
|
||||
handleFavorite: (options: {
|
||||
id: string[];
|
||||
isFavorite: boolean;
|
||||
itemType: LibraryItem;
|
||||
serverId: string;
|
||||
}) => void;
|
||||
handlePlayQueueAdd: ((options: PlayQueueAddOptions) => void) | undefined;
|
||||
itemType: LibraryItem;
|
||||
playButtonBehavior: Play;
|
||||
route: CardRoute;
|
||||
};
|
||||
data: any;
|
||||
isLoading?: boolean;
|
||||
controls: {
|
||||
cardRows: CardRow<Album>[] | CardRow<Artist>[] | CardRow<AlbumArtist>[];
|
||||
handleFavorite: (options: {
|
||||
id: string[];
|
||||
isFavorite: boolean;
|
||||
itemType: LibraryItem;
|
||||
serverId: string;
|
||||
}) => void;
|
||||
handlePlayQueueAdd: ((options: PlayQueueAddOptions) => void) | undefined;
|
||||
itemType: LibraryItem;
|
||||
playButtonBehavior: Play;
|
||||
route: CardRoute;
|
||||
};
|
||||
data: any;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const PosterCardContainer = styled.div<{ $isHidden?: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
opacity: ${({ $isHidden }) => ($isHidden ? 0 : 1)};
|
||||
pointer-events: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
opacity: ${({ $isHidden }) => ($isHidden ? 0 : 1)};
|
||||
pointer-events: auto;
|
||||
|
||||
.card-controls {
|
||||
opacity: 0;
|
||||
}
|
||||
.card-controls {
|
||||
opacity: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const ImageContainerStyles = css`
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
aspect-ratio: 1/1;
|
||||
overflow: hidden;
|
||||
background: var(--card-default-bg);
|
||||
border-radius: var(--card-poster-radius);
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
aspect-ratio: 1/1;
|
||||
overflow: hidden;
|
||||
background: var(--card-default-bg);
|
||||
border-radius: var(--card-poster-radius);
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 100%) 35%, rgba(0, 0, 0, 0%) 100%);
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease-in-out;
|
||||
content: '';
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::before {
|
||||
opacity: 0.5;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 100%) 35%, rgba(0, 0, 0, 0%) 100%);
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease-in-out;
|
||||
content: '';
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .card-controls {
|
||||
opacity: 1;
|
||||
}
|
||||
&:hover {
|
||||
&::before {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .card-controls {
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const ImageContainer = styled(Link)<{ $isFavorite?: boolean }>`
|
||||
${ImageContainerStyles}
|
||||
${ImageContainerStyles}
|
||||
`;
|
||||
|
||||
const ImageContainerSkeleton = styled.div`
|
||||
${ImageContainerStyles}
|
||||
${ImageContainerStyles}
|
||||
`;
|
||||
|
||||
const Image = styled(SimpleImg)`
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100% !important;
|
||||
max-height: 100%;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100% !important;
|
||||
max-height: 100%;
|
||||
border: 0;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
img {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
`;
|
||||
|
||||
const DetailContainer = styled.div`
|
||||
margin-top: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
`;
|
||||
|
||||
export const PosterCard = ({
|
||||
data,
|
||||
controls,
|
||||
isLoading,
|
||||
uniqueId,
|
||||
data,
|
||||
controls,
|
||||
isLoading,
|
||||
uniqueId,
|
||||
}: BaseGridCardProps & { uniqueId: string }) => {
|
||||
if (!isLoading) {
|
||||
const path = generatePath(
|
||||
controls.route.route,
|
||||
controls.route.slugs?.reduce((acc, slug) => {
|
||||
return {
|
||||
...acc,
|
||||
[slug.slugProperty]: data[slug.idProperty],
|
||||
};
|
||||
}, {}),
|
||||
);
|
||||
if (!isLoading) {
|
||||
const path = generatePath(
|
||||
controls.route.route,
|
||||
controls.route.slugs?.reduce((acc, slug) => {
|
||||
return {
|
||||
...acc,
|
||||
[slug.slugProperty]: data[slug.idProperty],
|
||||
};
|
||||
}, {}),
|
||||
);
|
||||
|
||||
let Placeholder = RiAlbumFill;
|
||||
let Placeholder = RiAlbumFill;
|
||||
|
||||
switch (controls.itemType) {
|
||||
case LibraryItem.ALBUM:
|
||||
Placeholder = RiAlbumFill;
|
||||
break;
|
||||
case LibraryItem.ARTIST:
|
||||
Placeholder = RiUserVoiceFill;
|
||||
break;
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
Placeholder = RiUserVoiceFill;
|
||||
break;
|
||||
case LibraryItem.PLAYLIST:
|
||||
Placeholder = RiPlayListFill;
|
||||
break;
|
||||
default:
|
||||
Placeholder = RiAlbumFill;
|
||||
break;
|
||||
switch (controls.itemType) {
|
||||
case LibraryItem.ALBUM:
|
||||
Placeholder = RiAlbumFill;
|
||||
break;
|
||||
case LibraryItem.ARTIST:
|
||||
Placeholder = RiUserVoiceFill;
|
||||
break;
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
Placeholder = RiUserVoiceFill;
|
||||
break;
|
||||
case LibraryItem.PLAYLIST:
|
||||
Placeholder = RiPlayListFill;
|
||||
break;
|
||||
default:
|
||||
Placeholder = RiAlbumFill;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<PosterCardContainer key={`${uniqueId}-${data.id}`}>
|
||||
<ImageContainer
|
||||
$isFavorite={data?.userFavorite}
|
||||
to={path}
|
||||
>
|
||||
{data?.imageUrl ? (
|
||||
<Image
|
||||
importance="auto"
|
||||
placeholder={data?.imagePlaceholderUrl || 'var(--card-default-bg)'}
|
||||
src={data?.imageUrl}
|
||||
/>
|
||||
) : (
|
||||
<Center
|
||||
sx={{
|
||||
background: 'var(--placeholder-bg)',
|
||||
borderRadius: 'var(--card-default-radius)',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Placeholder
|
||||
color="var(--placeholder-fg)"
|
||||
size={35}
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
<GridCardControls
|
||||
handleFavorite={controls.handleFavorite}
|
||||
handlePlayQueueAdd={controls.handlePlayQueueAdd}
|
||||
itemData={data}
|
||||
itemType={controls.itemType}
|
||||
/>
|
||||
</ImageContainer>
|
||||
<DetailContainer>
|
||||
<CardRows
|
||||
data={data}
|
||||
rows={controls.cardRows}
|
||||
/>
|
||||
</DetailContainer>
|
||||
</PosterCardContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PosterCardContainer key={`${uniqueId}-${data.id}`}>
|
||||
<ImageContainer
|
||||
$isFavorite={data?.userFavorite}
|
||||
to={path}
|
||||
>
|
||||
{data?.imageUrl ? (
|
||||
<Image
|
||||
importance="auto"
|
||||
placeholder={data?.imagePlaceholderUrl || 'var(--card-default-bg)'}
|
||||
src={data?.imageUrl}
|
||||
/>
|
||||
) : (
|
||||
<Center
|
||||
sx={{
|
||||
background: 'var(--placeholder-bg)',
|
||||
borderRadius: 'var(--card-default-radius)',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Placeholder
|
||||
color="var(--placeholder-fg)"
|
||||
size={35}
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
<GridCardControls
|
||||
handleFavorite={controls.handleFavorite}
|
||||
handlePlayQueueAdd={controls.handlePlayQueueAdd}
|
||||
itemData={data}
|
||||
itemType={controls.itemType}
|
||||
/>
|
||||
</ImageContainer>
|
||||
<DetailContainer>
|
||||
<CardRows
|
||||
data={data}
|
||||
rows={controls.cardRows}
|
||||
/>
|
||||
</DetailContainer>
|
||||
</PosterCardContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PosterCardContainer key={`placeholder-${uniqueId}-${data.id}`}>
|
||||
<Skeleton
|
||||
visible
|
||||
radius="sm"
|
||||
>
|
||||
<ImageContainerSkeleton />
|
||||
</Skeleton>
|
||||
<DetailContainer>
|
||||
<Stack spacing="sm">
|
||||
{controls.cardRows.map((row, index) => (
|
||||
<PosterCardContainer key={`placeholder-${uniqueId}-${data.id}`}>
|
||||
<Skeleton
|
||||
key={`${index}-${row.arrayProperty}`}
|
||||
visible
|
||||
height={14}
|
||||
radius="sm"
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</DetailContainer>
|
||||
</PosterCardContainer>
|
||||
);
|
||||
visible
|
||||
radius="sm"
|
||||
>
|
||||
<ImageContainerSkeleton />
|
||||
</Skeleton>
|
||||
<DetailContainer>
|
||||
<Stack spacing="sm">
|
||||
{controls.cardRows.map((row, index) => (
|
||||
<Skeleton
|
||||
key={`${index}-${row.arrayProperty}`}
|
||||
visible
|
||||
height={14}
|
||||
radius="sm"
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</DetailContainer>
|
||||
</PosterCardContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue