mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-12 23:33:43 +08:00
127 lines
3.9 KiB
TypeScript
127 lines
3.9 KiB
TypeScript
/**
|
|
* HistoryItem - Individual watch history item
|
|
* Displays video thumbnail, title, episode, progress, and delete button
|
|
*/
|
|
|
|
import Image from 'next/image';
|
|
import { Icons } from '@/components/ui/Icon';
|
|
import { formatTime, formatDate } from '@/lib/utils/format-utils';
|
|
import { PosterImage } from './PosterImage';
|
|
import { FavoriteButton } from '@/components/favorites/FavoriteButton';
|
|
|
|
interface HistoryItemProps {
|
|
videoId: string | number;
|
|
source: string;
|
|
title: string;
|
|
poster?: string;
|
|
episodeIndex: number;
|
|
episodes?: Array<{ name: string }>;
|
|
playbackPosition: number;
|
|
duration: number;
|
|
timestamp: number;
|
|
onRemove: () => void;
|
|
}
|
|
|
|
export function HistoryItem({
|
|
videoId,
|
|
source,
|
|
title,
|
|
poster,
|
|
episodeIndex,
|
|
episodes,
|
|
playbackPosition,
|
|
duration,
|
|
timestamp,
|
|
onRemove,
|
|
}: HistoryItemProps) {
|
|
const getVideoUrl = (): string => {
|
|
const params = new URLSearchParams({
|
|
id: videoId.toString(),
|
|
source,
|
|
title,
|
|
episode: episodeIndex.toString(),
|
|
});
|
|
return `/player?${params.toString()}`;
|
|
};
|
|
|
|
const handleClick = (event: React.MouseEvent) => {
|
|
// Middle mouse or Ctrl/Cmd+click opens in new tab
|
|
if (event.button === 1 || event.ctrlKey || event.metaKey) {
|
|
event.preventDefault();
|
|
window.open(getVideoUrl(), '_blank');
|
|
return;
|
|
}
|
|
};
|
|
|
|
const progress = (playbackPosition / duration) * 100;
|
|
const episodeText = episodes && episodes.length > 0
|
|
? episodes[episodeIndex]?.name || `第${episodeIndex + 1}集`
|
|
: '';
|
|
|
|
return (
|
|
<div className="group bg-[color-mix(in_srgb,var(--glass-bg)_50%,transparent)] rounded-[var(--radius-2xl)] p-3 hover:bg-[color-mix(in_srgb,var(--accent-color)_10%,transparent)] transition-all border border-transparent hover:border-[var(--glass-border)]">
|
|
<a
|
|
href={getVideoUrl()}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
handleClick(e as any);
|
|
if (!e.ctrlKey && !e.metaKey) {
|
|
window.location.href = getVideoUrl();
|
|
}
|
|
}}
|
|
onAuxClick={(e) => handleClick(e as any)}
|
|
className="block"
|
|
>
|
|
<div className="flex gap-3">
|
|
{/* Poster */}
|
|
<PosterImage poster={poster} title={title} progress={progress} />
|
|
|
|
{/* Info */}
|
|
<div className="flex-1 min-w-0">
|
|
<h3 className="text-sm font-medium text-[var(--text-color)] truncate group-hover:text-[var(--accent-color)] transition-colors mb-1">
|
|
{title}
|
|
</h3>
|
|
{episodeText && (
|
|
<p className="text-xs text-[var(--text-color-secondary)] mb-1">
|
|
{episodeText}
|
|
</p>
|
|
)}
|
|
<div className="flex items-center justify-between text-xs text-[var(--text-color-secondary)]">
|
|
<span>{formatTime(playbackPosition)} / {formatTime(duration)}</span>
|
|
<span>{formatDate(timestamp)}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
<div className="flex flex-col gap-1 self-start opacity-0 group-hover:opacity-100 transition-opacity">
|
|
{/* Favorite button */}
|
|
<FavoriteButton
|
|
videoId={videoId}
|
|
source={source}
|
|
title={title}
|
|
poster={poster}
|
|
remarks={episodeText}
|
|
size={14}
|
|
className="!p-1.5 !bg-transparent !border-0 !shadow-none hover:!bg-[var(--glass-bg)]"
|
|
showTooltip={false}
|
|
/>
|
|
|
|
{/* Delete button */}
|
|
<button
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
onRemove();
|
|
}}
|
|
className="p-1.5 hover:bg-[var(--glass-bg)] rounded-full cursor-pointer"
|
|
aria-label="删除"
|
|
>
|
|
<Icons.Trash size={14} className="text-[var(--text-color-secondary)]" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
);
|
|
}
|