mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-07-06 23:01:46 +08:00
feat(VideoLibrary): enhance cover rendering and loading skeletons
This commit is contained in:
@@ -141,43 +141,88 @@ export const VideoLibraryApp: React.FC<AppOpenComponentProps> = () => {
|
|||||||
|
|
||||||
const renderCover = (item: VideoLibraryItem) => {
|
const renderCover = (item: VideoLibraryItem) => {
|
||||||
const label = item.type === 'tv' ? 'TV' : 'Movie';
|
const label = item.type === 'tv' ? 'TV' : 'Movie';
|
||||||
const subtitle = item.type === 'tv'
|
const coverUrl = tmdbImage(item.poster_path, 'w342') || tmdbImage(item.backdrop_path, 'w780');
|
||||||
? `${item.seasons_count || 0} ${t('Seasons')} · ${item.episodes_count || 0} ${t('Episodes')}`
|
|
||||||
: (item.year ? String(item.year) : '');
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
height: 150,
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
aspectRatio: '2 / 3',
|
||||||
background: 'linear-gradient(135deg, #0b1020 0%, #22314a 55%, #2b3b5c 100%)',
|
background: 'linear-gradient(135deg, #0b1020 0%, #22314a 55%, #2b3b5c 100%)',
|
||||||
display: 'flex',
|
overflow: 'hidden',
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
padding: 12,
|
|
||||||
color: 'rgba(255,255,255,0.92)',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8 }}>
|
{coverUrl ? (
|
||||||
|
<Image
|
||||||
|
src={coverUrl}
|
||||||
|
alt={item.title || label}
|
||||||
|
preview={false}
|
||||||
|
wrapperStyle={{ width: '100%', height: '100%', display: 'block' }}
|
||||||
|
style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
|
||||||
|
fallback="/logo.svg"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: 'rgba(255,255,255,0.7)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VideoCameraOutlined style={{ fontSize: 28 }} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
background: 'linear-gradient(180deg, rgba(0,0,0,0.48) 0%, rgba(0,0,0,0.12) 40%, rgba(0,0,0,0.45) 100%)',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 10,
|
||||||
|
left: 10,
|
||||||
|
right: 10,
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Tag color={item.type === 'tv' ? 'geekblue' : 'gold'} style={{ marginInlineEnd: 0 }}>
|
<Tag color={item.type === 'tv' ? 'geekblue' : 'gold'} style={{ marginInlineEnd: 0 }}>
|
||||||
{label}
|
{label}
|
||||||
</Tag>
|
</Tag>
|
||||||
<VideoCameraOutlined style={{ fontSize: 18, opacity: 0.9 }} />
|
<VideoCameraOutlined style={{ fontSize: 18, opacity: 0.9, color: 'rgba(255,255,255,0.9)' }} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
||||||
<PlayCircleOutlined style={{ fontSize: 32, opacity: 0.9 }} />
|
<div
|
||||||
<div style={{ minWidth: 0 }}>
|
style={{
|
||||||
<Typography.Text
|
position: 'absolute',
|
||||||
strong
|
left: 10,
|
||||||
style={{ color: 'rgba(255,255,255,0.92)', display: 'block' }}
|
bottom: 10,
|
||||||
ellipsis
|
width: 36,
|
||||||
>
|
height: 36,
|
||||||
{item.title || '--'}
|
borderRadius: 999,
|
||||||
</Typography.Text>
|
background: 'rgba(2,6,23,0.55)',
|
||||||
{subtitle && (
|
border: '1px solid rgba(255,255,255,0.22)',
|
||||||
<Typography.Text style={{ color: 'rgba(255,255,255,0.72)', fontSize: 12 }}>
|
display: 'flex',
|
||||||
{subtitle}
|
alignItems: 'center',
|
||||||
</Typography.Text>
|
justifyContent: 'center',
|
||||||
)}
|
color: 'rgba(255,255,255,0.92)',
|
||||||
</div>
|
boxShadow: token.boxShadowTertiary,
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlayCircleOutlined style={{ fontSize: 20 }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -543,14 +588,18 @@ export const VideoLibraryApp: React.FC<AppOpenComponentProps> = () => {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(210px, 1fr))', gap: 12 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(210px, 1fr))', gap: 12 }}>
|
||||||
{Array.from({ length: 10 }).map((_, idx) => (
|
{Array.from({ length: 10 }).map((_, idx) => (
|
||||||
<Card
|
<Card
|
||||||
key={idx}
|
key={idx}
|
||||||
size="small"
|
size="small"
|
||||||
style={{ borderRadius: 12, overflow: 'hidden' }}
|
style={{ borderRadius: 12, overflow: 'hidden' }}
|
||||||
cover={<Skeleton.Image active style={{ width: '100%', height: 150 }} />}
|
cover={(
|
||||||
>
|
<div style={{ width: '100%', aspectRatio: '2 / 3' }}>
|
||||||
<Skeleton active paragraph={{ rows: 2 }} />
|
<Skeleton.Image active style={{ width: '100%', height: '100%' }} />
|
||||||
</Card>
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Skeleton active paragraph={{ rows: 2 }} />
|
||||||
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : filtered.length === 0 ? (
|
) : filtered.length === 0 ? (
|
||||||
@@ -604,7 +653,7 @@ export const VideoLibraryApp: React.FC<AppOpenComponentProps> = () => {
|
|||||||
title={detailTitle || selected?.title || t('Details')}
|
title={detailTitle || selected?.title || t('Details')}
|
||||||
open={detailOpen}
|
open={detailOpen}
|
||||||
onClose={closeDetail}
|
onClose={closeDetail}
|
||||||
width={900}
|
width="100%"
|
||||||
destroyOnHidden
|
destroyOnHidden
|
||||||
getContainer={false}
|
getContainer={false}
|
||||||
styles={{ body: { padding: 0 } }}
|
styles={{ body: { padding: 0 } }}
|
||||||
|
|||||||
Reference in New Issue
Block a user