more filling out of game page

This commit is contained in:
roormonger
2026-03-25 21:18:44 -04:00
parent 2efd6cae9a
commit 3d593b83f0
2 changed files with 104 additions and 1 deletions

View File

@@ -33,6 +33,8 @@ export interface DetailedGame extends Game {
favorite?: boolean;
manualUrl?: string;
platformId?: number;
videoUrl?: string; // Direct video link
youtubeId?: string; // YouTube ID for embed
}
export interface RommCollection {
@@ -309,7 +311,6 @@ export const rommApiClient = {
genres: getAll('genres').map(sanitize).filter(Boolean),
franchises: getAll('franchises').map(sanitize).filter(Boolean),
releaseDate: formatDate(json.release_date || getFirst('first_release_date') || getFirst('release_date')),
screenshots: (json.screenshots || []).map((s: any) => s.url ? getFullImageUrl(s.url) || '' : ''),
fsName: json.fs_name,
regions: regions.length > 0 ? regions : ['Global'],
players: (() => {
@@ -338,6 +339,13 @@ export const rommApiClient = {
favorite: (json.user_collections || json.rom_collections || []).some((c: any) => c.is_favorite || c.name === 'Favorites'),
manualUrl: getFullImageUrl(json.url_manual),
platformId: json.platform_id,
videoUrl: getFullImageUrl(json.url_video || json.ss_metadata?.video_url),
youtubeId: json.youtube_video_id || json.igdb_metadata?.youtube_video_id || json.launchbox_metadata?.youtube_video_id,
screenshots: Array.from(new Set([
...(json.merged_screenshots || []).map((s: string) => getFullImageUrl(s)),
...(json.screenshots || []).map((s: any) => s.url ? getFullImageUrl(s.url) || '' : ''),
...(getFirst('screenshots') || [])
])).filter(Boolean) as string[],
};
},

View File

@@ -154,6 +154,7 @@ export const GamesPage = () => {
const [playingGameId, setPlayingGameId] = useState<string | null>(null);
const [isCollectionModalOpen, setIsCollectionModalOpen] = useState(false);
const [isManualModalOpen, setIsManualModalOpen] = useState(false);
const [activeMediaIndex, setActiveMediaIndex] = useState(0);
const [activeZone, setActiveZone] = useState<'platforms' | 'games' | 'alphabet' | 'details' | null>(null);
const detailTimeoutRef = React.useRef<any>(null);
@@ -308,6 +309,29 @@ export const GamesPage = () => {
}
};
// Consolidate media for Galleria
const mediaItems = React.useMemo(() => {
if (!detailsQuery.data) return [];
const items: { type: 'video' | 'image', url: string, youtubeId?: string }[] = [];
if (detailsQuery.data.youtubeId) {
items.push({ type: 'video', url: '', youtubeId: detailsQuery.data.youtubeId });
} else if (detailsQuery.data.videoUrl) {
items.push({ type: 'video', url: detailsQuery.data.videoUrl });
}
if (detailsQuery.data.screenshots) {
detailsQuery.data.screenshots.forEach(s => {
items.push({ type: 'image', url: s });
});
}
return items;
}, [detailsQuery.data]);
useEffect(() => {
setActiveMediaIndex(0);
}, [selectedGameId]);
const updateFocusFromScroll = (container: HTMLElement) => {
const rect = container.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
@@ -660,6 +684,77 @@ export const GamesPage = () => {
</div>
</div>
{/* Media Galleria Container */}
{mediaItems.length > 0 && (
<div className="mt-12 grid grid-cols-12 gap-8">
<div className="col-span-6 bg-white/5 rounded-[12px] p-5 border border-white/5 flex flex-col shadow-2xl">
{/* Active Media Slot */}
<div className="aspect-video w-full rounded-[10px] bg-black/40 border border-white/10 overflow-hidden relative mb-5 group">
{mediaItems[activeMediaIndex].type === 'video' ? (
mediaItems[activeMediaIndex].youtubeId ? (
<iframe
src={`https://www.youtube.com/embed/${mediaItems[activeMediaIndex].youtubeId}?autoplay=0&controls=1&rel=0&modestbranding=1`}
className="w-full h-full"
allowFullScreen
></iframe>
) : (
<video
key={mediaItems[activeMediaIndex].url}
src={mediaItems[activeMediaIndex].url}
controls
className="w-full h-full object-cover"
/>
)
) : (
<img
src={mediaItems[activeMediaIndex].url}
className="w-full h-full object-cover animate-fade-in"
alt="Gallery Preview"
/>
)}
</div>
{/* Thumbnail Row */}
<div className="flex gap-3 overflow-x-auto pb-2 scrollbar-hide">
{mediaItems.map((item, idx) => (
<FocusableItem
key={idx}
onFocus={() => {
if (mode === 'gamepad') setActiveZone('details');
setActiveMediaIndex(idx);
}}
onClick={() => setActiveMediaIndex(idx)}
className="shrink-0"
focusKey={`THUMB_${idx}`}
>
{(focused) => (
<div className={`w-[124px] aspect-video rounded-[6px] border-2 transition-all duration-300 overflow-hidden relative ${focused || activeMediaIndex === idx ? 'border-[#2563eb] scale-105 z-10' : 'border-white/10 opacity-40 grayscale hover:opacity-100 hover:grayscale-0'}`}>
{item.type === 'video' ? (
<div className="w-full h-full bg-black/60 flex items-center justify-center">
<span className="material-symbols-outlined text-[#2563eb] text-3xl filled">play_circle</span>
</div>
) : (
<img src={item.url} className="w-full h-full object-cover" alt="" />
)}
{activeMediaIndex === idx && (
<div className="absolute inset-0 bg-[#2563eb]/20 pointer-events-none"></div>
)}
</div>
)}
</FocusableItem>
))}
</div>
</div>
{/* Placeholder for the "something else" mentioned by user */}
<div className="col-span-6 bg-white/5 rounded-[12px] p-5 border border-white/5 border-dashed flex items-center justify-center opacity-20">
<div className="text-center geist-mono uppercase tracking-[0.3em] text-[10px]">
Future Content Zone
</div>
</div>
</div>
)}
</div>
) : selectedGameId ? (
<div className="h-full flex items-center justify-center">