mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 08:15:48 +00:00
Add image carousel to GameView
This commit is contained in:
Generated
+20
@@ -49,6 +49,7 @@
|
|||||||
"react-confetti-boom": "^1.0.0",
|
"react-confetti-boom": "^1.0.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"react-router": "7.5.2",
|
"react-router": "7.5.2",
|
||||||
|
"swiper": "^11.2.6",
|
||||||
"yup": "^1.6.1"
|
"yup": "^1.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -16296,6 +16297,25 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/swiper": {
|
||||||
|
"version": "11.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.6.tgz",
|
||||||
|
"integrity": "sha512-8aXpYKtjy3DjcbzZfz+/OX/GhcU5h+looA6PbAzHMZT6ESSycSp9nAjPCenczgJyslV+rUGse64LMGpWE3PX9Q==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/swiperjs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "open_collective",
|
||||||
|
"url": "http://opencollective.com/swiper"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tabbable": {
|
"node_modules/tabbable": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
"react-confetti-boom": "^1.0.0",
|
"react-confetti-boom": "^1.0.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"react-router": "7.5.2",
|
"react-router": "7.5.2",
|
||||||
|
"swiper": "^11.2.6",
|
||||||
"yup": "^1.6.1"
|
"yup": "^1.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import {Autoplay, Virtual} from 'swiper/modules';
|
||||||
|
import {Swiper, SwiperSlide} from "swiper/react";
|
||||||
|
import {Image} from "@heroui/react";
|
||||||
|
|
||||||
|
import "swiper/css";
|
||||||
|
import "swiper/css/navigation";
|
||||||
|
import "swiper/css/pagination";
|
||||||
|
import "swiper/css/autoplay";
|
||||||
|
|
||||||
|
interface ImageCarouselProps {
|
||||||
|
imageIds: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SlideData {
|
||||||
|
isActive: boolean;
|
||||||
|
isVisible: boolean;
|
||||||
|
isPrev: boolean;
|
||||||
|
isNext: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ImageCarousel({imageIds}: ImageCarouselProps) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-2 bg-transparent">
|
||||||
|
<Swiper
|
||||||
|
modules={[Virtual, Autoplay]}
|
||||||
|
virtual={true}
|
||||||
|
slidesPerView={3}
|
||||||
|
spaceBetween={0}
|
||||||
|
autoplay={{
|
||||||
|
delay: 5000,
|
||||||
|
waitForTransition: false,
|
||||||
|
pauseOnMouseEnter: true
|
||||||
|
}}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<SwiperSlide key={imageIds[imageIds.length]} virtualIndex={0}>
|
||||||
|
<Image
|
||||||
|
src={`/images/screenshot/${imageIds[imageIds.length - 1]}`}
|
||||||
|
alt={`Game screenshot slide ${imageIds.length + 2}`}
|
||||||
|
className="w-full h-full object-cover aspect-[16/9] scale-90"
|
||||||
|
/>
|
||||||
|
</SwiperSlide>
|
||||||
|
{imageIds.map((imageId, index) => (
|
||||||
|
<SwiperSlide key={imageId} virtualIndex={index + 1}>
|
||||||
|
{({isNext}: SlideData) => (
|
||||||
|
<Image
|
||||||
|
src={`/images/screenshot/${imageId}`}
|
||||||
|
alt={`Game screenshot slide ${index + 1}`}
|
||||||
|
className={`w-full h-full object-cover aspect-[16/9] ${!isNext ? "scale-90" : ""}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
<SwiperSlide key={imageIds[0]} virtualIndex={imageIds.length + 2}>
|
||||||
|
<Image
|
||||||
|
src={`/images/screenshot/${imageIds[0]}`}
|
||||||
|
alt={`Game screenshot slide ${0}`}
|
||||||
|
className="w-full h-full object-cover aspect-[16/9] scale-90"
|
||||||
|
/>
|
||||||
|
</SwiperSlide>
|
||||||
|
</Swiper>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
/* Overwrite default Hilla styles (e.g. loading indicator) */
|
/* Overwrite default Hilla styles (e.g. loading indicator) */
|
||||||
:root {
|
:root {
|
||||||
--lumo-primary-color: theme('colors.primary');
|
--lumo-primary-color: theme(colors.primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* List box drag & drop */
|
/* List box drag & drop */
|
||||||
@@ -27,5 +27,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.react-aria-DropIndicator[data-drop-target] {
|
.react-aria-DropIndicator[data-drop-target] {
|
||||||
outline: 1px solid theme('colors.primary');
|
outline: 1px solid theme(colors.primary);
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import {GameEndpoint} from "Frontend/generated/endpoints";
|
|||||||
import {useParams} from "react-router";
|
import {useParams} from "react-router";
|
||||||
import {GameCover} from "Frontend/components/general/covers/GameCover";
|
import {GameCover} from "Frontend/components/general/covers/GameCover";
|
||||||
import ComboButton, {ComboButtonOption} from "Frontend/components/general/input/ComboButton";
|
import ComboButton, {ComboButtonOption} from "Frontend/components/general/input/ComboButton";
|
||||||
|
import ImageCarousel from "Frontend/components/general/covers/ImageCarousel";
|
||||||
|
|
||||||
export default function GameView() {
|
export default function GameView() {
|
||||||
const {gameId} = useParams();
|
const {gameId} = useParams();
|
||||||
@@ -37,9 +38,13 @@ export default function GameView() {
|
|||||||
return (game && (
|
return (game && (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{game.imageIds !== undefined && game.imageIds.length > 0 &&
|
{game.imageIds !== undefined && game.imageIds.length > 0 &&
|
||||||
<div className="overflow-hidden rounded-lg">
|
<div className="overflow-hidden rounded-lg relative">
|
||||||
<img className="w-full h-96 object-cover brightness-50 blur-sm scale-110" alt="Game screenshot"
|
<img className="w-full h-96 object-cover brightness-50 blur-sm scale-110"
|
||||||
src={`/images/screenshot/${game.imageIds[0]}`}/>
|
alt="Game screenshot"
|
||||||
|
src={`/images/screenshot/${game.imageIds[0]}`}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 pointer-events-none bg-gradient-to-b from-transparent to-background"/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div className="flex flex-col gap-4 mx-24">
|
<div className="flex flex-col gap-4 mx-24">
|
||||||
@@ -55,10 +60,17 @@ export default function GameView() {
|
|||||||
</div>
|
</div>
|
||||||
<ComboButton options={downloadOptions} preferredOptionKey="preferred-download-method"/>
|
<ComboButton options={downloadOptions} preferredOptionKey="preferred-download-method"/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col gap-8">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<p className="text-foreground/60">Summary</p>
|
<p className="text-foreground/60">Summary</p>
|
||||||
<p>{game.summary}</p>
|
<p>{game.summary}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col gap-2 overflow-visible">
|
||||||
|
<p className="text-foreground/60">Screenshots</p>
|
||||||
|
{game.imageIds !== undefined && game.imageIds.length > 0 &&
|
||||||
|
<ImageCarousel imageIds={game.imageIds}/>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
|
|||||||
Reference in New Issue
Block a user