Compare commits

..

No commits in common. "5fac68e809cecd0404e81d239406b252086df7a7" and "74980b5059abe991c98d3badb191220b68f5d1e1" have entirely different histories.

6 changed files with 60 additions and 149 deletions

Binary file not shown.

View File

@ -1,30 +1,6 @@
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getDatabase } from '@/db'; import { getDatabase } from '@/db';
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
try {
const mediaId = parseInt(id);
if (isNaN(mediaId)) {
return NextResponse.json({ error: 'Invalid media ID' }, { status: 400 });
}
const db = getDatabase();
// Check if bookmark exists
const bookmark = db.prepare(`
SELECT id FROM bookmarks WHERE media_id = ?
`).get(mediaId) as { id: number } | undefined;
return NextResponse.json({
isBookmarked: !!bookmark,
bookmarkId: bookmark?.id || null
});
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) { export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params; const { id } = await params;
try { try {

View File

@ -1,30 +1,6 @@
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getDatabase } from '@/db'; import { getDatabase } from '@/db';
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
try {
const mediaId = parseInt(id);
if (isNaN(mediaId)) {
return NextResponse.json({ error: 'Invalid media ID' }, { status: 400 });
}
const db = getDatabase();
// Check if rating exists for this media
const star = db.prepare(`
SELECT rating FROM stars WHERE media_id = ?
`).get(mediaId) as { rating: number } | undefined;
return NextResponse.json({
hasRating: !!star,
rating: star?.rating || 0
});
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
export async function DELETE(request: Request, { params }: { params: Promise<{ id: string }> }) { export async function DELETE(request: Request, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params; const { id } = await params;
const db = getDatabase(); const db = getDatabase();

View File

@ -215,12 +215,12 @@ const FolderViewerPage = () => {
// Handle rating operations // Handle rating operations
const handleRate = async (mediaId: number, rating: number) => { const handleRate = async (mediaId: number, rating: number) => {
try { try {
const response = await fetch(`/api/stars`, { const response = await fetch(`/api/stars/${mediaId}`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ mediaId, rating }) body: JSON.stringify({ rating })
}); });
if (response.ok) { if (response.ok) {

View File

@ -173,7 +173,35 @@ export default function ArtPlayerWrapper({
], ],
// Custom layer for bookmark and rating controls // Custom layer for bookmark and rating controls
layers: [], layers: showBookmarks || showRatings ? [
{
html: `<div class="artplayer-custom-controls absolute top-4 right-16 flex items-center gap-2 z-15">
${showBookmarks ? `<div class="artplayer-bookmark-control flex items-center gap-1 px-2 py-1 rounded hover:bg-black/50 transition-colors cursor-pointer text-white bg-black/30">
<svg class="h-4 w-4" fill="${localIsBookmarked ? 'currentColor' : 'none'}" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"></path>
</svg>
<span class="text-xs">${localBookmarkCount}</span>
</div>` : ''}
${showRatings ? `<div class="artplayer-rating-control flex items-center gap-1 px-2 py-1 rounded hover:bg-black/50 transition-colors cursor-pointer text-white bg-black/30">
<span class="text-xs"> ${localAvgRating.toFixed(1)}</span>
</div>` : ''}
</div>`,
style: {
position: 'absolute',
top: '16px',
right: '64px', // Positioned to the left of close button
zIndex: '15'
} as any,
click: function(this: any, component: any, event: Event) {
const target = event.target as HTMLElement;
if (target.closest('.artplayer-bookmark-control')) {
handleBookmarkToggle();
} else if (target.closest('.artplayer-rating-control')) {
handleRatingClick();
}
}
}
] : [],
// Custom initialization for HLS // Custom initialization for HLS
customType: { customType: {
@ -360,55 +388,49 @@ export default function ArtPlayerWrapper({
setError(`Failed to initialize player: ${error instanceof Error ? error.message : 'Unknown error'}`); setError(`Failed to initialize player: ${error instanceof Error ? error.message : 'Unknown error'}`);
setIsLoading(false); setIsLoading(false);
} }
}, [useArtPlayer, isOpen, video, onProgress, volume, autoplay, format?.supportLevel]); }, [useArtPlayer, isOpen, video, onProgress, volume, autoplay, format?.supportLevel, localIsBookmarked, localBookmarkCount, localAvgRating]);
// Handle bookmark toggle // Handle bookmark toggle
const handleBookmarkToggle = useCallback(async () => { const handleBookmarkToggle = useCallback(async () => {
if (!video.id) return; if (!video.id) return;
try { try {
setLocalIsBookmarked(prev => { if (localIsBookmarked) {
const newBookmarked = !prev; if (onUnbookmark) {
if (newBookmarked) { onUnbookmark(video.id);
if (onBookmark) {
onBookmark(video.id);
}
setLocalBookmarkCount(prevCount => prevCount + 1);
} else {
if (onUnbookmark) {
onUnbookmark(video.id);
}
setLocalBookmarkCount(prevCount => Math.max(0, prevCount - 1));
} }
return newBookmarked; setLocalIsBookmarked(false);
}); setLocalBookmarkCount(prev => Math.max(0, prev - 1));
} else {
if (onBookmark) {
onBookmark(video.id);
}
setLocalIsBookmarked(true);
setLocalBookmarkCount(prev => prev + 1);
}
} catch (error) { } catch (error) {
console.error('Error toggling bookmark:', error); console.error('Error toggling bookmark:', error);
} }
}, [video.id, onBookmark, onUnbookmark]); }, [video.id, localIsBookmarked, onBookmark, onUnbookmark]);
// Handle rating click // Handle rating click
const handleRatingClick = useCallback(() => { const handleRatingClick = useCallback(() => {
if (!video.id || !onRate) return; if (!video.id || !onRate) return;
setLocalAvgRating(prev => { const currentRating = Math.round(localAvgRating);
const currentRating = Math.round(prev); const newRating = currentRating >= 5 ? 1 : currentRating + 1;
const newRating = currentRating >= 5 ? 1 : currentRating + 1;
onRate(video.id, newRating); onRate(video.id, newRating);
return newRating; setLocalAvgRating(newRating);
}); }, [video.id, localAvgRating, onRate]);
}, [video.id, onRate]);
// Handle individual star click for rating // Handle individual star click for rating
const handleStarClick = useCallback((rating: number) => { const handleStarClick = useCallback((rating: number) => {
if (onRate) { if (onRate) {
const currentRating = Math.round(localAvgRating); onRate(video.id, rating);
// If clicking the same star as current rating, cancel the rating setLocalAvgRating(rating);
const newRating = currentRating === rating ? 0 : rating;
onRate(video.id, newRating);
setLocalAvgRating(newRating);
} }
}, [onRate, video.id, localAvgRating]); }, [onRate, video.id]);
// Format time display // Format time display
const formatTime = (time: number) => { const formatTime = (time: number) => {
@ -628,9 +650,10 @@ export default function ArtPlayerWrapper({
? 'bg-yellow-500/30 text-yellow-300 hover:bg-yellow-500/40' ? 'bg-yellow-500/30 text-yellow-300 hover:bg-yellow-500/40'
: 'bg-white/20 text-white hover:bg-white/30' : 'bg-white/20 text-white hover:bg-white/30'
}`} }`}
title={localIsBookmarked ? 'Remove bookmark' : 'Add bookmark'} title="Toggle bookmark"
> >
<Bookmark className={`h-4 w-4 ${localIsBookmarked ? 'fill-current' : ''} drop-shadow-md`} /> <Bookmark className={`h-4 w-4 ${localIsBookmarked ? 'fill-current' : ''} drop-shadow-md`} />
<span className="text-sm font-medium drop-shadow-md">{localBookmarkCount}</span>
</button> </button>
)} )}

View File

@ -39,60 +39,6 @@ export default function UnifiedVideoPlayer({
}: UnifiedVideoPlayerProps) { }: UnifiedVideoPlayerProps) {
const [format, setFormat] = useState<ReturnType<typeof detectVideoFormat> | null>(null); const [format, setFormat] = useState<ReturnType<typeof detectVideoFormat> | null>(null);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [isBookmarked, setIsBookmarked] = useState(false);
const [bookmarkCheckLoading, setBookmarkCheckLoading] = useState(true);
const [currentRating, setCurrentRating] = useState(0);
const [ratingCheckLoading, setRatingCheckLoading] = useState(true);
// Check current bookmark status and rating when video opens
useEffect(() => {
if (isOpen && video.id) {
checkBookmarkStatus();
checkRatingStatus();
}
}, [isOpen, video.id]);
const checkBookmarkStatus = async () => {
if (!video.id) return;
setBookmarkCheckLoading(true);
try {
const response = await fetch(`/api/bookmarks/${video.id}`);
if (response.ok) {
const data = await response.json();
setIsBookmarked(data.isBookmarked || false);
} else {
setIsBookmarked(false);
}
} catch (error) {
console.error('Error checking bookmark status:', error);
setIsBookmarked(false);
} finally {
setBookmarkCheckLoading(false);
}
};
const checkRatingStatus = async () => {
if (!video.id) return;
setRatingCheckLoading(true);
try {
const response = await fetch(`/api/stars/${video.id}`);
if (response.ok) {
const data = await response.json();
setCurrentRating(data.rating || 0);
} else {
setCurrentRating(0);
}
} catch (error) {
console.error('Error checking rating status:', error);
setCurrentRating(0);
} finally {
setRatingCheckLoading(false);
}
};
// Detect format on mount // Detect format on mount
useEffect(() => { useEffect(() => {
@ -134,23 +80,13 @@ export default function UnifiedVideoPlayer({
const handleBookmarkToggle = useCallback(async (videoId: number) => { const handleBookmarkToggle = useCallback(async (videoId: number) => {
if (onBookmark) { if (onBookmark) {
await onBookmark(videoId); await onBookmark(videoId);
setIsBookmarked(true); // Update local state
} }
}, [onBookmark]); }, [onBookmark]);
// Handle unbookmark
const handleUnbookmark = useCallback(async (videoId: number) => {
if (onUnbookmark) {
await onUnbookmark(videoId);
setIsBookmarked(false); // Update local state
}
}, [onUnbookmark]);
// Handle rating // Handle rating
const handleRatingUpdate = useCallback(async (videoId: number, rating: number) => { const handleRatingUpdate = useCallback(async (videoId: number, rating: number) => {
if (onRate) { if (onRate) {
await onRate(videoId, rating); await onRate(videoId, rating);
setCurrentRating(rating); // Update local state
} }
}, [onRate]); }, [onRate]);
@ -164,13 +100,13 @@ export default function UnifiedVideoPlayer({
onClose={onClose} onClose={onClose}
onProgress={handleProgressUpdate} onProgress={handleProgressUpdate}
onBookmark={handleBookmarkToggle} onBookmark={handleBookmarkToggle}
onUnbookmark={handleUnbookmark} onUnbookmark={onUnbookmark}
onRate={handleRatingUpdate} onRate={handleRatingUpdate}
onError={handleArtPlayerError} onError={handleArtPlayerError}
useArtPlayer={true} useArtPlayer={true}
isBookmarked={isBookmarked} isBookmarked={(video.bookmark_count || 0) > 0}
bookmarkCount={video.bookmark_count || 0} bookmarkCount={video.bookmark_count || 0}
avgRating={currentRating} avgRating={video.avg_rating || 0}
showBookmarks={showBookmarks} showBookmarks={showBookmarks}
showRatings={showRatings} showRatings={showRatings}
autoplay={autoplay} autoplay={autoplay}
@ -178,7 +114,7 @@ export default function UnifiedVideoPlayer({
); );
}; };
if (isLoading || bookmarkCheckLoading || ratingCheckLoading) { if (isLoading) {
return ( return (
<div className="fixed inset-0 bg-black/90 z-50 flex items-center justify-center"> <div className="fixed inset-0 bg-black/90 z-50 flex items-center justify-center">
<div className="text-white text-center"> <div className="text-white text-center">