Compare commits

..

No commits in common. "854afd4c4154866fa9a7fd5034b803444bc23f6c" and "848578c136ec7606b5b8dce5b3425bb0bdb577d7" have entirely different histories.

6 changed files with 13 additions and 157 deletions

View File

@ -28,11 +28,6 @@ UI:
8. the video card can be clicked, once clicked, a poped up video player will be displayed. it can be closed, fast forward, expand to full screen, etc. 8. the video card can be clicked, once clicked, a poped up video player will be displayed. it can be closed, fast forward, expand to full screen, etc.
9. can bookmark/un-bookmark the video, can star the video 9. can bookmark/un-bookmark the video, can star the video
Deployment:
1. use docker compose to deploy the service
2. Dockerfile should be defined
3. docker-compose.yml should be defined.
Development Rules: Development Rules:
1. Everytime after making all the changes, run 'pnpm build' to verify the changes are compiling correct. 1. Everytime after making all the changes, run 'pnpm build' to verify the changes are compiling correct.
2. Once added debug logs, don't delete it until told so. 2. Once added debug logs, don't delete it until told so.

View File

@ -148,6 +148,7 @@ const FolderViewerPage = () => {
const handlePhotoClick = (item: FileSystemItem, index: number) => { const handlePhotoClick = (item: FileSystemItem, index: number) => {
if (item.type === 'photo' && item.id) { if (item.type === 'photo' && item.id) {
setSelectedPhoto(item); setSelectedPhoto(item);
setCurrentPhotoIndex(index);
setIsPhotoViewerOpen(true); setIsPhotoViewerOpen(true);
} }
}; };
@ -157,40 +158,16 @@ const FolderViewerPage = () => {
setSelectedPhoto(null); setSelectedPhoto(null);
}; };
const [currentItems, setCurrentItems] = useState<FileSystemItem[]>([]);
const handleNextPhoto = () => { const handleNextPhoto = () => {
// Navigate to next photo, skipping videos // This would need to be implemented with the current items list
const photos = currentItems.filter(item => item.type === 'photo' && item.id); // For now, just close the viewer
if (photos.length === 0) return; handleClosePhotoViewer();
const currentPhotoId = selectedPhoto?.id;
if (!currentPhotoId) return;
const currentIndexInPhotos = photos.findIndex(p => p.id === currentPhotoId);
if (currentIndexInPhotos === -1) return;
const nextIndex = (currentIndexInPhotos + 1) % photos.length;
const nextPhoto = photos[nextIndex];
setSelectedPhoto(nextPhoto);
}; };
const handlePrevPhoto = () => { const handlePrevPhoto = () => {
// Navigate to previous photo, skipping videos // This would need to be implemented with the current items list
const photos = currentItems.filter(item => item.type === 'photo' && item.id); // For now, just close the viewer
if (photos.length === 0) return; handleClosePhotoViewer();
const currentPhotoId = selectedPhoto?.id;
if (!currentPhotoId) return;
const currentIndexInPhotos = photos.findIndex(p => p.id === currentPhotoId);
if (currentIndexInPhotos === -1) return;
const prevIndex = (currentIndexInPhotos - 1 + photos.length) % photos.length;
const prevPhoto = photos[prevIndex];
setSelectedPhoto(prevPhoto);
}; };
if (!path) { if (!path) {
@ -223,7 +200,6 @@ const FolderViewerPage = () => {
onBreadcrumbClick={handleBreadcrumbClick} onBreadcrumbClick={handleBreadcrumbClick}
breadcrumbs={getBreadcrumbs(path)} breadcrumbs={getBreadcrumbs(path)}
libraries={libraries} libraries={libraries}
onItemsLoaded={setCurrentItems}
/> />
{/* Photo Viewer */} {/* Photo Viewer */}
@ -233,7 +209,7 @@ const FolderViewerPage = () => {
onClose={handleClosePhotoViewer} onClose={handleClosePhotoViewer}
onNext={handleNextPhoto} onNext={handleNextPhoto}
onPrev={handlePrevPhoto} onPrev={handlePrevPhoto}
showNavigation={true} showNavigation={false}
showBookmarks={false} showBookmarks={false}
showRatings={false} showRatings={false}
formatFileSize={formatFileSize} formatFileSize={formatFileSize}

View File

@ -19,49 +19,12 @@ interface Photo {
export default function PhotosPage() { export default function PhotosPage() {
const [selectedPhoto, setSelectedPhoto] = useState<Photo | null>(null); const [selectedPhoto, setSelectedPhoto] = useState<Photo | null>(null);
const [isViewerOpen, setIsViewerOpen] = useState(false); const [isViewerOpen, setIsViewerOpen] = useState(false);
const [photosList, setPhotosList] = useState<Photo[]>([]);
const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0);
const handlePhotoClick = (photo: Photo, index?: number) => { const handlePhotoClick = (photo: Photo) => {
setSelectedPhoto(photo); setSelectedPhoto(photo);
if (index !== undefined) {
setCurrentPhotoIndex(index);
}
setIsViewerOpen(true); setIsViewerOpen(true);
}; };
const handlePhotosData = (photos: Photo[]) => {
setPhotosList(photos);
};
const handleNextPhoto = () => {
if (photosList.length === 0) return;
const currentPhotoId = selectedPhoto?.id;
if (!currentPhotoId) return;
const currentIndex = photosList.findIndex(p => p.id === currentPhotoId);
if (currentIndex === -1) return;
const nextIndex = (currentIndex + 1) % photosList.length;
setSelectedPhoto(photosList[nextIndex]);
setCurrentPhotoIndex(nextIndex);
};
const handlePrevPhoto = () => {
if (photosList.length === 0) return;
const currentPhotoId = selectedPhoto?.id;
if (!currentPhotoId) return;
const currentIndex = photosList.findIndex(p => p.id === currentPhotoId);
if (currentIndex === -1) return;
const prevIndex = (currentIndex - 1 + photosList.length) % photosList.length;
setSelectedPhoto(photosList[prevIndex]);
setCurrentPhotoIndex(prevIndex);
};
const handleCloseViewer = () => { const handleCloseViewer = () => {
setIsViewerOpen(false); setIsViewerOpen(false);
setSelectedPhoto(null); setSelectedPhoto(null);
@ -103,7 +66,6 @@ export default function PhotosPage() {
onBookmark={handleBookmark} onBookmark={handleBookmark}
onUnbookmark={handleUnbookmark} onUnbookmark={handleUnbookmark}
onRate={handleRate} onRate={handleRate}
onDataUpdate={handlePhotosData}
/> />
{/* Photo Viewer */} {/* Photo Viewer */}
@ -111,9 +73,6 @@ export default function PhotosPage() {
photo={selectedPhoto!} photo={selectedPhoto!}
isOpen={isViewerOpen} isOpen={isViewerOpen}
onClose={handleCloseViewer} onClose={handleCloseViewer}
onNext={handleNextPhoto}
onPrev={handlePrevPhoto}
showNavigation={true}
showBookmarks={true} showBookmarks={true}
showRatings={true} showRatings={true}
onBookmark={handleBookmark} onBookmark={handleBookmark}

View File

@ -21,11 +21,10 @@ interface MediaItem {
interface InfiniteVirtualGridProps { interface InfiniteVirtualGridProps {
type: 'video' | 'photo' | 'bookmark'; type: 'video' | 'photo' | 'bookmark';
onItemClick: (item: MediaItem, index?: number) => void; onItemClick: (item: MediaItem) => void;
onBookmark: (id: number) => Promise<void>; onBookmark: (id: number) => Promise<void>;
onUnbookmark: (id: number) => Promise<void>; onUnbookmark: (id: number) => Promise<void>;
onRate: (id: number, rating: number) => Promise<void>; onRate: (id: number, rating: number) => Promise<void>;
onDataUpdate?: (items: MediaItem[]) => void;
} }
const ITEM_HEIGHT = 220; const ITEM_HEIGHT = 220;
@ -37,8 +36,7 @@ export default function InfiniteVirtualGrid({
onItemClick, onItemClick,
onBookmark, onBookmark,
onUnbookmark, onUnbookmark,
onRate, onRate
onDataUpdate
}: InfiniteVirtualGridProps) { }: InfiniteVirtualGridProps) {
const [totalItems, setTotalItems] = useState(0); const [totalItems, setTotalItems] = useState(0);
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
@ -326,23 +324,6 @@ export default function InfiniteVirtualGrid({
fetchItems(nextBatchStart, batchEnd); fetchItems(nextBatchStart, batchEnd);
} }
} }
// Collect all available items and notify parent
if (onDataUpdate) {
const allItems: MediaItem[] = [];
const sortedKeys = Array.from(dataCacheRef.current.keys()).sort((a, b) => a - b);
for (const batchKey of sortedKeys) {
const items = dataCacheRef.current.get(batchKey);
if (items) {
allItems.push(...items);
}
}
if (allItems.length > 0) {
onDataUpdate(allItems);
}
}
}, [totalItems, fetchItems, getColumnCount]); }, [totalItems, fetchItems, getColumnCount]);
const Cell = ({ columnIndex, rowIndex, style }: any) => { const Cell = ({ columnIndex, rowIndex, style }: any) => {
@ -371,7 +352,7 @@ export default function InfiniteVirtualGrid({
<div style={style} className="p-2"> <div style={style} className="p-2">
<Card <Card
className="group hover:shadow-lg transition-all duration-300 hover:-translate-y-1 cursor-pointer border-border overflow-hidden h-full" className="group hover:shadow-lg transition-all duration-300 hover:-translate-y-1 cursor-pointer border-border overflow-hidden h-full"
onClick={() => onItemClick(item, index)} onClick={() => onItemClick(item)}
> >
<div className="relative overflow-hidden bg-muted aspect-video"> <div className="relative overflow-hidden bg-muted aspect-video">
<img <img

View File

@ -86,53 +86,6 @@ export default function VideoViewer({
} }
}, [isOpen, video]); }, [isOpen, video]);
// Keyboard shortcuts
useEffect(() => {
if (!isOpen) return;
const handleKeyDown = (e: KeyboardEvent) => {
if (!videoRef.current) return;
switch (e.key) {
case 'Escape':
e.preventDefault();
onClose();
break;
case 'ArrowRight':
e.preventDefault();
videoRef.current.currentTime = Math.min(
videoRef.current.currentTime + 10,
videoRef.current.duration || 0
);
break;
case 'ArrowLeft':
e.preventDefault();
videoRef.current.currentTime = Math.max(
videoRef.current.currentTime - 10,
0
);
break;
case ' ':
e.preventDefault();
handlePlayPause();
break;
case 'f':
case 'F':
e.preventDefault();
handleFullscreen();
break;
case 'm':
case 'M':
e.preventDefault();
handleMute();
break;
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [isOpen, onClose]);
const handlePlayPause = () => { const handlePlayPause = () => {
if (videoRef.current) { if (videoRef.current) {
if (isPlaying) { if (isPlaying) {

View File

@ -34,7 +34,6 @@ interface VirtualizedFolderGridProps {
onBreadcrumbClick: (path: string) => void; onBreadcrumbClick: (path: string) => void;
breadcrumbs: BreadcrumbItem[]; breadcrumbs: BreadcrumbItem[];
libraries: {id: number, path: string}[]; libraries: {id: number, path: string}[];
onItemsLoaded?: (items: FileSystemItem[]) => void;
} }
const ITEM_HEIGHT = 280; // Increased for folder cards const ITEM_HEIGHT = 280; // Increased for folder cards
@ -46,8 +45,7 @@ export default function VirtualizedFolderGrid({
onBackClick, onBackClick,
onBreadcrumbClick, onBreadcrumbClick,
breadcrumbs, breadcrumbs,
libraries, libraries
onItemsLoaded
}: VirtualizedFolderGridProps) { }: VirtualizedFolderGridProps) {
const [items, setItems] = useState<FileSystemItem[]>([]); const [items, setItems] = useState<FileSystemItem[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -205,12 +203,6 @@ export default function VirtualizedFolderGrid({
} }
}, [currentPath, fetchItems]); }, [currentPath, fetchItems]);
useEffect(() => {
if (onItemsLoaded) {
onItemsLoaded(items);
}
}, [items, onItemsLoaded]);
const Cell = ({ columnIndex, rowIndex, style }: any) => { const Cell = ({ columnIndex, rowIndex, style }: any) => {
const columnCount = getColumnCount(); const columnCount = getColumnCount();
const index = rowIndex * columnCount + columnIndex; const index = rowIndex * columnCount + columnIndex;