diff --git a/gameyfin/package-lock.json b/gameyfin/package-lock.json index d2140cc..52c0726 100644 --- a/gameyfin/package-lock.json +++ b/gameyfin/package-lock.json @@ -48,6 +48,7 @@ "react-aria-components": "^1.7.1", "react-confetti-boom": "^1.0.0", "react-dom": "18.3.1", + "react-player": "^2.16.0", "react-router": "7.5.2", "swiper": "^11.2.6", "yup": "^1.6.1" @@ -13992,6 +13993,12 @@ "@types/trusted-types": "^2.0.2" } }, + "node_modules/load-script": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz", + "integrity": "sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==", + "license": "MIT" + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -14066,6 +14073,12 @@ "node": ">= 0.4" } }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "license": "MIT" + }, "node_modules/merge-source-map": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz", @@ -15130,6 +15143,28 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/react-player": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/react-player/-/react-player-2.16.0.tgz", + "integrity": "sha512-mAIPHfioD7yxO0GNYVFD1303QFtI3lyyQZLY229UEAp/a10cSW+hPcakg0Keq8uWJxT2OiT/4Gt+Lc9bD6bJmQ==", + "license": "MIT", + "dependencies": { + "deepmerge": "^4.0.0", + "load-script": "^1.0.0", + "memoize-one": "^5.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.0.1" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/react-player/node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", diff --git a/gameyfin/package.json b/gameyfin/package.json index da38758..553b07c 100644 --- a/gameyfin/package.json +++ b/gameyfin/package.json @@ -43,6 +43,7 @@ "react-aria-components": "^1.7.1", "react-confetti-boom": "^1.0.0", "react-dom": "18.3.1", + "react-player": "^2.16.0", "react-router": "7.5.2", "swiper": "^11.2.6", "yup": "^1.6.1" @@ -129,7 +130,9 @@ "react-aria-components": "$react-aria-components", "react-accessible-treeview": "$react-accessible-treeview", "rand-seed": "$rand-seed", - "react-router": "$react-router" + "react-router": "$react-router", + "swiper": "$swiper", + "react-player": "$react-player" }, "vaadin": { "dependencies": { @@ -190,6 +193,6 @@ "workbox-core": "7.3.0", "workbox-precaching": "7.3.0" }, - "hash": "fdf5506c7d7915b341632254a47867cd7d7007cab8d08447bd909b37cdb94cf9" + "hash": "b7f2b9b343406ec77ce90fe42104642df3b7205f522d2cc60db71051097a32de" } } \ No newline at end of file diff --git a/gameyfin/src/main/frontend/components/general/covers/ImageCarousel.tsx b/gameyfin/src/main/frontend/components/general/covers/ImageCarousel.tsx index 13e436c..84247a6 100644 --- a/gameyfin/src/main/frontend/components/general/covers/ImageCarousel.tsx +++ b/gameyfin/src/main/frontend/components/general/covers/ImageCarousel.tsx @@ -1,14 +1,18 @@ import {Autoplay, Virtual} from 'swiper/modules'; import {Swiper, SwiperSlide} from "swiper/react"; -import {Image} from "@heroui/react"; +import {Card, Image, Modal, ModalContent, useDisclosure} from "@heroui/react"; +import ReactPlayer from 'react-player'; import "swiper/css"; import "swiper/css/navigation"; import "swiper/css/pagination"; import "swiper/css/autoplay"; +import {useEffect, useState} from "react"; + interface ImageCarouselProps { - imageIds: number[]; + imageUrls?: string[]; + videosUrls?: string[]; } interface SlideData { @@ -18,48 +22,108 @@ interface SlideData { isNext: boolean; } -export default function ImageCarousel({imageIds}: ImageCarouselProps) { +export default function ImageCarousel({imageUrls, videosUrls}: ImageCarouselProps) { + + interface CarouselElement { + type: "image" | "video"; + url: string; + } + + const SLIDES_PER_VIEW = 3; + + const [elements, setElements] = useState(); + const [selectedImageUrl, setSelectedImageUrl] = useState(); + const imagePopup = useDisclosure(); + + useEffect(() => { + const images = imageUrls?.map((imageUrl) => ({ + type: "image" as const, + url: imageUrl + })) || []; + const videos = videosUrls?.map((videoUrl) => ({ + type: "video" as const, + url: videoUrl + })) || []; + + if ((images.length + videos.length) > SLIDES_PER_VIEW) { + let elements = [...videos, ...images]; + // Add the last element to the start of the array and the first element to the end of the array to create a loop + setElements([elements[elements.length - 1], ...elements, elements[0]]); + } else { + setElements([...videos, ...images]); + } + }, []) + + function showImagePopup(imageUrl: string) { + setSelectedImageUrl(imageUrl); + imagePopup.onOpen(); + } return (
- - {`Game - - {imageIds.map((imageId, index) => ( - - {({isNext}: SlideData) => ( - {`Game - )} + {elements && elements.map((e, index) => ( + + {({isNext}: SlideData) => { + if (e.type === "image") { + return ( + {`Game showImagePopup(e.url)} + /> + ) + } + return ( + + + + ) + }} ))} - - {`Game - +
); +} + +function ImagePopup({imageUrl, isOpen, onOpenChange}: { + imageUrl?: string, + isOpen: boolean, + onOpenChange: (isOpen: boolean) => void +}) { + return (imageUrl && + + + {(onClose) => ( +
+ Game screenshot +
+ )} +
+
+ ) } \ No newline at end of file diff --git a/gameyfin/src/main/frontend/views/GameView.tsx b/gameyfin/src/main/frontend/views/GameView.tsx index 2c539be..83b32c9 100644 --- a/gameyfin/src/main/frontend/views/GameView.tsx +++ b/gameyfin/src/main/frontend/views/GameView.tsx @@ -66,9 +66,12 @@ export default function GameView() {

{game.summary}

-

Screenshots

+

Media

{game.imageIds !== undefined && game.imageIds.length > 0 && - } + `/images/screenshot/${id}`)} + videosUrls={game.videoUrls} + />}