美化继续观看模块
This commit is contained in:
@@ -10,8 +10,8 @@ import {
|
||||
subscribeToDataUpdates,
|
||||
} from '@/lib/db.client';
|
||||
|
||||
import ScrollableRow from '@/components/ScrollableRow';
|
||||
import VideoCard from '@/components/VideoCard';
|
||||
import ScrollableRow from '@/components/ScrollableRow';
|
||||
|
||||
interface ContinueWatchingProps {
|
||||
className?: string;
|
||||
@@ -106,16 +106,16 @@ export default function ContinueWatching({ className }: ContinueWatchingProps) {
|
||||
<ScrollableRow>
|
||||
{loading
|
||||
? // 加载状态显示灰色占位数据
|
||||
Array.from({ length: 6 }).map((_, index) => (
|
||||
Array.from({ length: 8 }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
|
||||
className='min-w-[180px] w-48 sm:min-w-[200px] sm:w-52'
|
||||
>
|
||||
<div className='relative aspect-[2/3] w-full overflow-hidden rounded-lg bg-gray-200 animate-pulse dark:bg-gray-800'>
|
||||
<div className='relative aspect-[3/2] w-full overflow-hidden rounded-lg bg-gray-200 animate-pulse dark:bg-gray-800'>
|
||||
<div className='absolute inset-0 bg-gray-300 dark:bg-gray-700'></div>
|
||||
</div>
|
||||
<div className='mt-2 h-4 bg-gray-200 rounded animate-pulse dark:bg-gray-800'></div>
|
||||
<div className='mt-1 h-3 bg-gray-200 rounded animate-pulse dark:bg-gray-800'></div>
|
||||
<div className='mt-1 h-1 bg-gray-200 rounded animate-pulse dark:bg-gray-800'></div>
|
||||
<div className='mt-2 h-4 bg-gray-200 rounded animate-pulse dark:bg-gray-800 w-3/4'></div>
|
||||
</div>
|
||||
))
|
||||
: // 显示真实数据
|
||||
@@ -124,7 +124,7 @@ export default function ContinueWatching({ className }: ContinueWatchingProps) {
|
||||
return (
|
||||
<div
|
||||
key={record.key}
|
||||
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
|
||||
className='min-w-[180px] w-48 sm:min-w-[200px] sm:w-52'
|
||||
>
|
||||
<VideoCard
|
||||
id={id}
|
||||
@@ -145,6 +145,9 @@ export default function ContinueWatching({ className }: ContinueWatchingProps) {
|
||||
}
|
||||
type={record.total_episodes > 1 ? 'tv' : ''}
|
||||
origin={record.origin}
|
||||
orientation='horizontal'
|
||||
playTime={record.play_time}
|
||||
totalTime={record.total_time}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -23,7 +23,7 @@ interface FavoriteItem {
|
||||
source_name?: string;
|
||||
currentEpisode?: number;
|
||||
search_title?: string;
|
||||
origin?: string;
|
||||
origin?: 'vod' | 'live';
|
||||
}
|
||||
|
||||
interface FavoritesPanelProps {
|
||||
@@ -97,14 +97,13 @@ export const FavoritesPanel: React.FC<FavoritesPanelProps> = ({
|
||||
|
||||
// 监听收藏变化,实时移除已取消收藏的项目
|
||||
useEffect(() => {
|
||||
const unsubscribe = subscribeToDataUpdates(async (event) => {
|
||||
if (event === 'favoritesUpdated' && isOpen) {
|
||||
// 获取最新的收藏列表
|
||||
const allFavorites = await getAllFavorites();
|
||||
const currentKeys = Object.keys(allFavorites);
|
||||
|
||||
const unsubscribe = subscribeToDataUpdates('favoritesUpdated', async (newFavorites: Record<string, any>) => {
|
||||
if (isOpen) {
|
||||
// 获取最新的收藏列表的键
|
||||
const currentKeys = Object.keys(newFavorites);
|
||||
|
||||
// 过滤掉已经不在收藏中的项目
|
||||
setFavoriteItems((prevItems) =>
|
||||
setFavoriteItems((prevItems) =>
|
||||
prevItems.filter((item) => {
|
||||
const key = `${item.source}+${item.id}`;
|
||||
return currentKeys.includes(key);
|
||||
|
||||
@@ -104,7 +104,7 @@ export default function ScrollableRow({
|
||||
>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`flex space-x-6 overflow-x-auto scrollbar-hide py-1 sm:py-2 ${bottomPadding} px-4 sm:px-6`}
|
||||
className={`flex space-x-2 sm:space-x-4 overflow-x-auto scrollbar-hide py-1 sm:py-2 ${bottomPadding} px-4 sm:px-6`}
|
||||
onScroll={checkScroll}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -51,6 +51,9 @@ export interface VideoCardProps {
|
||||
isUpcoming?: boolean; // 是否为即将上映
|
||||
seasonNumber?: number; // 季度编号
|
||||
seasonName?: string; // 季度名称
|
||||
orientation?: 'vertical' | 'horizontal'; // 卡片方向
|
||||
playTime?: number; // 当前播放时间(秒)
|
||||
totalTime?: number; // 总时长(秒)
|
||||
}
|
||||
|
||||
export type VideoCardHandle = {
|
||||
@@ -84,6 +87,9 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
|
||||
isUpcoming = false,
|
||||
seasonNumber,
|
||||
seasonName,
|
||||
orientation = 'vertical',
|
||||
playTime,
|
||||
totalTime,
|
||||
}: VideoCardProps,
|
||||
ref
|
||||
) {
|
||||
@@ -584,7 +590,11 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
|
||||
>
|
||||
{/* 海报容器 */}
|
||||
<div
|
||||
className={`relative aspect-[2/3] overflow-hidden rounded-lg ${origin === 'live' ? 'ring-1 ring-gray-300/80 dark:ring-gray-600/80' : ''}`}
|
||||
className={`relative overflow-hidden rounded-lg ${origin === 'live' ? 'ring-1 ring-gray-300/80 dark:ring-gray-600/80' : ''} ${
|
||||
orientation === 'horizontal'
|
||||
? 'aspect-[3/2]'
|
||||
: 'aspect-[2/3]'
|
||||
}`}
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
@@ -596,13 +606,13 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
|
||||
}}
|
||||
>
|
||||
{/* 骨架屏 */}
|
||||
{!isLoading && <ImagePlaceholder aspectRatio='aspect-[2/3]' />}
|
||||
{!isLoading && <ImagePlaceholder aspectRatio={orientation === 'horizontal' ? 'aspect-[3/2]' : 'aspect-[2/3]'} />}
|
||||
{/* 图片 */}
|
||||
<Image
|
||||
src={processImageUrl(actualPoster)}
|
||||
alt={actualTitle}
|
||||
fill
|
||||
className={origin === 'live' ? 'object-contain' : 'object-cover'}
|
||||
className={origin === 'live' ? 'object-contain' : orientation === 'horizontal' ? 'object-cover object-center' : 'object-cover'}
|
||||
referrerPolicy='no-referrer'
|
||||
loading='lazy'
|
||||
onLoadingComplete={() => setIsLoading(true)}
|
||||
@@ -816,7 +826,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
|
||||
</div>
|
||||
)}
|
||||
|
||||
{actualEpisodes && actualEpisodes > 1 && (
|
||||
{actualEpisodes && actualEpisodes > 1 && orientation === 'vertical' && (
|
||||
<div
|
||||
className='absolute top-2 right-2 bg-green-500 text-white text-xs font-semibold px-2 py-1 rounded-md shadow-md transition-all duration-300 ease-out group-hover:scale-110'
|
||||
style={{
|
||||
@@ -999,112 +1009,13 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
|
||||
{/* 进度条 */}
|
||||
{config.showProgress && progress !== undefined && (
|
||||
<div
|
||||
className='mt-1 h-1 w-full bg-gray-200 rounded-full overflow-hidden'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className='h-full bg-green-500 transition-all duration-500 ease-out'
|
||||
style={{
|
||||
width: `${progress}%`,
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 标题与来源 */}
|
||||
<div
|
||||
className='mt-2 text-center'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className='relative'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
<span
|
||||
className='block text-sm font-semibold truncate text-gray-900 dark:text-gray-100 transition-colors duration-300 ease-in-out group-hover:text-green-600 dark:group-hover:text-green-400 peer'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{actualTitle}
|
||||
</span>
|
||||
{/* 自定义 tooltip */}
|
||||
<div
|
||||
className='absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-1 bg-gray-800 text-white text-xs rounded-md shadow-lg opacity-0 invisible peer-hover:opacity-100 peer-hover:visible transition-all duration-200 ease-out delay-100 whitespace-nowrap pointer-events-none'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{actualTitle}
|
||||
{/* 横向模式:标题和进度条在海报上 */}
|
||||
{orientation === 'horizontal' && (
|
||||
<>
|
||||
{/* 顶部渐变遮罩 - 用于标题背景 */}
|
||||
<div
|
||||
className='absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-gray-800'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
{config.showSourceName && source_name && (
|
||||
<span
|
||||
className='block text-xs text-gray-500 dark:text-gray-400 mt-1'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className='inline-block border rounded px-2 py-0.5 border-gray-500/60 dark:border-gray-400/60 transition-all duration-300 ease-in-out group-hover:border-green-500/60 group-hover:text-green-600 dark:group-hover:text-green-400'
|
||||
className='absolute top-0 left-0 right-0 bg-gradient-to-b from-black/80 via-black/40 to-transparent pt-2 pb-8 px-2'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
@@ -1115,14 +1026,307 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{origin === 'live' && (
|
||||
<Radio size={12} className="inline-block text-gray-500 dark:text-gray-400 mr-1.5" />
|
||||
{/* 标题 */}
|
||||
<div
|
||||
className='mb-1'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
<span
|
||||
className='block text-sm font-bold truncate text-white'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
title={actualTitle}
|
||||
>
|
||||
{actualTitle}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 集数信息 - 只有超过1集时才显示 */}
|
||||
{currentEpisode && actualEpisodes && actualEpisodes > 1 && (
|
||||
<div
|
||||
className='text-xs text-white/90'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
第{currentEpisode}集 · 共{actualEpisodes}集
|
||||
</div>
|
||||
)}
|
||||
{source_name}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 底部渐变遮罩 - 用于进度条背景 */}
|
||||
<div
|
||||
className='absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 via-black/60 to-transparent pt-8 pb-2 px-2'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{/* 进度条 */}
|
||||
{config.showProgress && progress !== undefined && origin !== 'live' && (
|
||||
<div
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
{/* 来源和时长显示 - 在进度条上方 */}
|
||||
<div className='flex items-center justify-between mb-1'>
|
||||
{/* 时长显示 - 左侧 */}
|
||||
{from === 'playrecord' && playTime !== undefined && totalTime !== undefined && (
|
||||
<div
|
||||
className='text-[10px] text-white/80'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{(() => {
|
||||
const formatTime = (seconds: number) => {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
// 0分钟时不显示分钟
|
||||
if (mins === 0) {
|
||||
return `${secs}秒`;
|
||||
}
|
||||
return `${mins}分${secs}秒`;
|
||||
};
|
||||
return formatTime(playTime);
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 来源 - 右侧 */}
|
||||
{config.showSourceName && source_name && (
|
||||
<span
|
||||
className={`inline-block border rounded px-1 py-0.5 text-[8px] text-white/90 bg-black/30 backdrop-blur-sm ${
|
||||
actualSource === 'openlist' ? 'border-yellow-500' : 'border-white/60'
|
||||
}`}
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{source_name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className='h-1 w-full bg-white/20 rounded-full overflow-hidden'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className='h-full bg-white transition-all duration-500 ease-out'
|
||||
style={{
|
||||
width: `${progress}%`,
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 直播时只显示来源 */}
|
||||
{origin === 'live' && config.showSourceName && source_name && (
|
||||
<div className='flex items-center justify-end'>
|
||||
<span
|
||||
className={`inline-block border rounded px-1 py-0.5 text-[8px] text-white/90 bg-black/30 backdrop-blur-sm ${
|
||||
origin === 'live' ? 'border-red-500' : actualSource === 'openlist' ? 'border-yellow-500' : 'border-white/60'
|
||||
}`}
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<Radio size={8} className="inline-block text-white/90 mr-0.5" />
|
||||
{source_name}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 竖向模式:进度条和标题在海报下方 */}
|
||||
{orientation === 'vertical' && (
|
||||
<>
|
||||
{/* 进度条 */}
|
||||
{config.showProgress && progress !== undefined && (
|
||||
<div
|
||||
className='mt-1 h-1 w-full bg-gray-200 rounded-full overflow-hidden'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className='h-full bg-green-500 transition-all duration-500 ease-out'
|
||||
style={{
|
||||
width: `${progress}%`,
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 标题与来源 */}
|
||||
<div
|
||||
className='mt-2 text-center'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className='relative'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
<span
|
||||
className='block text-sm font-semibold truncate text-gray-900 dark:text-gray-100 transition-colors duration-300 ease-in-out group-hover:text-green-600 dark:group-hover:text-green-400 peer'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{actualTitle}
|
||||
</span>
|
||||
{/* 自定义 tooltip */}
|
||||
<div
|
||||
className='absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-1 bg-gray-800 text-white text-xs rounded-md shadow-lg opacity-0 invisible peer-hover:opacity-100 peer-hover:visible transition-all duration-200 ease-out delay-100 whitespace-nowrap pointer-events-none'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{actualTitle}
|
||||
<div
|
||||
className='absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-gray-800'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
{config.showSourceName && source_name && (
|
||||
<span
|
||||
className='block text-xs text-gray-500 dark:text-gray-400 mt-1'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className='inline-block border rounded px-2 py-0.5 border-gray-500/60 dark:border-gray-400/60 transition-all duration-300 ease-in-out group-hover:border-green-500/60 group-hover:text-green-600 dark:group-hover:text-green-400'
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
WebkitTouchCallout: 'none',
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{origin === 'live' && (
|
||||
<Radio size={12} className="inline-block text-gray-500 dark:text-gray-400 mr-1.5" />
|
||||
)}
|
||||
{source_name}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 操作菜单 - 支持右键和长按触发 */}
|
||||
|
||||
Reference in New Issue
Block a user