mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-03 00:33:49 +03:00
Merge pull request #200 from Mkdantas/main
Added screenshots and trailers to game details
This commit is contained in:
commit
748f3457f2
12433
package-lock.json
generated
Normal file
12433
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,7 @@
|
|||||||
<title>Hydra</title>
|
<title>Hydra</title>
|
||||||
<meta
|
<meta
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://steamcdn-a.akamaihd.net https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com;"
|
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://steamcdn-a.akamaihd.net https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com; media-src 'self' data: https://steamcdn-a.akamaihd.net https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com;"
|
||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body style="background-color: #1c1c1c">
|
<body style="background-color: #1c1c1c">
|
||||||
|
132
src/renderer/src/pages/game-details/gallery-slider.tsx
Normal file
132
src/renderer/src/pages/game-details/gallery-slider.tsx
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import { RefObject, useEffect, useRef, useState } from "react";
|
||||||
|
import { ShopDetails, SteamMovies, SteamScreenshot } from "@types";
|
||||||
|
import { ChevronRightIcon, ChevronLeftIcon } from "@primer/octicons-react";
|
||||||
|
import * as styles from "./game-details.css";
|
||||||
|
|
||||||
|
export interface GallerySliderProps {
|
||||||
|
gameDetails: ShopDetails | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GallerySlider({ gameDetails }: GallerySliderProps) {
|
||||||
|
const scrollContainerRef: RefObject<HTMLDivElement> =
|
||||||
|
useRef<HTMLDivElement>(null);
|
||||||
|
const [mediaCount] = useState<number>(() => {
|
||||||
|
if (gameDetails) {
|
||||||
|
if (gameDetails.screenshots && gameDetails.movies) {
|
||||||
|
return gameDetails.screenshots.length + gameDetails.movies.length;
|
||||||
|
} else if (gameDetails.movies) {
|
||||||
|
return gameDetails.movies.length;
|
||||||
|
} else if (gameDetails.screenshots) {
|
||||||
|
return gameDetails.screenshots.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
const [mediaIndex, setMediaIndex] = useState<number>(0);
|
||||||
|
const [arrowShow, setArrowShow] = useState(false);
|
||||||
|
|
||||||
|
const scrollHorizontallyToPercentage = () => {
|
||||||
|
if (scrollContainerRef.current) {
|
||||||
|
const container = scrollContainerRef.current;
|
||||||
|
const totalWidth = container.scrollWidth - container.clientWidth;
|
||||||
|
const itemWidth = totalWidth / (mediaCount - 1);
|
||||||
|
const scrollLeft = mediaIndex * itemWidth;
|
||||||
|
container.scrollLeft = scrollLeft;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const showNextImage = () => {
|
||||||
|
setMediaIndex((index: number) => {
|
||||||
|
if (index === mediaCount - 1) return 0;
|
||||||
|
|
||||||
|
return index + 1;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const showPrevImage = () => {
|
||||||
|
setMediaIndex((index: number) => {
|
||||||
|
if (index === 0) return mediaCount - 1;
|
||||||
|
|
||||||
|
return index - 1;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
scrollHorizontallyToPercentage();
|
||||||
|
}, [mediaIndex]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{gameDetails?.screenshots && (
|
||||||
|
<div className={styles.gallerySliderContainer}>
|
||||||
|
<div
|
||||||
|
onMouseEnter={() => setArrowShow(true)}
|
||||||
|
onMouseLeave={() => setArrowShow(false)}
|
||||||
|
className={styles.gallerySliderAnimationContainer}
|
||||||
|
>
|
||||||
|
{gameDetails.movies &&
|
||||||
|
gameDetails.movies.map((video: SteamMovies) => (
|
||||||
|
<video
|
||||||
|
controls
|
||||||
|
className={styles.gallerySliderMedia}
|
||||||
|
poster={video.thumbnail}
|
||||||
|
style={{ translate: `${-100 * mediaIndex}%` }}
|
||||||
|
>
|
||||||
|
<source src={video.webm.max.replace("http", "https")} />
|
||||||
|
</video>
|
||||||
|
))}
|
||||||
|
{gameDetails.screenshots &&
|
||||||
|
gameDetails.screenshots.map((image: SteamScreenshot) => (
|
||||||
|
<img
|
||||||
|
className={styles.gallerySliderMedia}
|
||||||
|
src={image.path_full}
|
||||||
|
style={{ translate: `${-100 * mediaIndex}%` }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{arrowShow && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={showPrevImage}
|
||||||
|
className={styles.gallerySliderButton}
|
||||||
|
style={{ left: 0 }}
|
||||||
|
>
|
||||||
|
<ChevronLeftIcon className={styles.gallerySliderIcons} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={showNextImage}
|
||||||
|
className={styles.gallerySliderButton}
|
||||||
|
style={{ right: 0 }}
|
||||||
|
>
|
||||||
|
<ChevronRightIcon className={styles.gallerySliderIcons} />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.gallerySliderPreview} ref={scrollContainerRef}>
|
||||||
|
{gameDetails.movies &&
|
||||||
|
gameDetails.movies.map((video: SteamMovies, i: number) => (
|
||||||
|
<img
|
||||||
|
onClick={() => setMediaIndex(i)}
|
||||||
|
src={video.thumbnail}
|
||||||
|
className={`${styles.gallerySliderMediaPreview} ${mediaIndex === i ? styles.gallerySliderMediaPreviewActive : ""}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{gameDetails.screenshots &&
|
||||||
|
gameDetails.screenshots.map(
|
||||||
|
(image: SteamScreenshot, i: number) => (
|
||||||
|
<img
|
||||||
|
onClick={() =>
|
||||||
|
setMediaIndex(
|
||||||
|
i + (gameDetails.movies ? gameDetails.movies.length : 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className={`${styles.gallerySliderMediaPreview} ${mediaIndex === i + (gameDetails.movies ? gameDetails.movies.length : 0) ? styles.gallerySliderMediaPreviewActive : ""}`}
|
||||||
|
src={image.path_full}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -79,6 +79,94 @@ export const descriptionContent = style({
|
|||||||
height: "100%",
|
height: "100%",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const gallerySliderContainer = style({
|
||||||
|
padding: `${SPACING_UNIT * 3}px ${SPACING_UNIT * 2}px`,
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const gallerySliderMedia = style({
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
display: "block",
|
||||||
|
flexShrink: 0,
|
||||||
|
flexGrow: 0,
|
||||||
|
transition: "translate 300ms ease-in-out",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const gallerySliderAnimationContainer = style({
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
position: "relative",
|
||||||
|
overflow: "hidden",
|
||||||
|
"@media": {
|
||||||
|
"(min-width: 1280px)": {
|
||||||
|
width: "60%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const gallerySliderPreview = style({
|
||||||
|
width: "100%",
|
||||||
|
paddingTop: "0.5rem",
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
position: "relative",
|
||||||
|
overflowX: "auto",
|
||||||
|
overflowY: "hidden",
|
||||||
|
"@media": {
|
||||||
|
"(min-width: 1280px)": {
|
||||||
|
width: "60%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"::-webkit-scrollbar-thumb": {
|
||||||
|
width: "20%"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const gallerySliderMediaPreview = style({
|
||||||
|
cursor: "pointer",
|
||||||
|
width: "20%",
|
||||||
|
height: "20%",
|
||||||
|
display: "block",
|
||||||
|
flexShrink: 0,
|
||||||
|
flexGrow: 0,
|
||||||
|
opacity: 0.3,
|
||||||
|
paddingRight: "5px",
|
||||||
|
transition: "translate 300ms ease-in-out",
|
||||||
|
":hover": {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const gallerySliderMediaPreviewActive = style({
|
||||||
|
opacity: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const gallerySliderButton = style({
|
||||||
|
all: "unset",
|
||||||
|
display: "block",
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
padding: "1rem",
|
||||||
|
cursor: "pointer",
|
||||||
|
transition: "background-color 100ms ease-in-out",
|
||||||
|
":hover": {
|
||||||
|
backgroundColor: "rgb(0,0,0, 0.2)",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const gallerySliderIcons = style({
|
||||||
|
stroke: "white",
|
||||||
|
fill: "black",
|
||||||
|
width: "2rem",
|
||||||
|
height: "2rem",
|
||||||
|
});
|
||||||
|
|
||||||
export const contentSidebar = style({
|
export const contentSidebar = style({
|
||||||
borderLeft: `solid 1px ${vars.color.border};`,
|
borderLeft: `solid 1px ${vars.color.border};`,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
@ -36,6 +36,7 @@ import {
|
|||||||
DONT_SHOW_ONLINE_FIX_INSTRUCTIONS_KEY,
|
DONT_SHOW_ONLINE_FIX_INSTRUCTIONS_KEY,
|
||||||
OnlineFixInstallationGuide,
|
OnlineFixInstallationGuide,
|
||||||
} from "./installation-guides";
|
} from "./installation-guides";
|
||||||
|
import { GallerySlider } from "./gallery-slider";
|
||||||
|
|
||||||
export function GameDetails() {
|
export function GameDetails() {
|
||||||
const { objectID, shop } = useParams();
|
const { objectID, shop } = useParams();
|
||||||
@ -89,7 +90,6 @@ export function GameDetails() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getGame();
|
getGame();
|
||||||
}, [getGame, gameDownloading?.id]);
|
}, [getGame, gameDownloading?.id]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setGame(null);
|
setGame(null);
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@ -249,6 +249,8 @@ export function GameDetails() {
|
|||||||
<div className={styles.descriptionContent}>
|
<div className={styles.descriptionContent}>
|
||||||
<DescriptionHeader gameDetails={gameDetails} />
|
<DescriptionHeader gameDetails={gameDetails} />
|
||||||
|
|
||||||
|
<GallerySlider gameDetails={gameDetails} />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: gameDetails?.about_the_game ?? "",
|
__html: gameDetails?.about_the_game ?? "",
|
||||||
|
@ -12,6 +12,20 @@ export interface SteamScreenshot {
|
|||||||
path_full: string;
|
path_full: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SteamVideoSource {
|
||||||
|
max: string;
|
||||||
|
'480': string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SteamMovies {
|
||||||
|
id: number;
|
||||||
|
mp4: SteamVideoSource;
|
||||||
|
webm: SteamVideoSource;
|
||||||
|
thumbnail: string;
|
||||||
|
name: string;
|
||||||
|
highlight: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SteamAppDetails {
|
export interface SteamAppDetails {
|
||||||
name: string;
|
name: string;
|
||||||
detailed_description: string;
|
detailed_description: string;
|
||||||
@ -19,6 +33,7 @@ export interface SteamAppDetails {
|
|||||||
short_description: string;
|
short_description: string;
|
||||||
publishers: string[];
|
publishers: string[];
|
||||||
genres: SteamGenre[];
|
genres: SteamGenre[];
|
||||||
|
movies: SteamMovies[];
|
||||||
screenshots: SteamScreenshot[];
|
screenshots: SteamScreenshot[];
|
||||||
pc_requirements: {
|
pc_requirements: {
|
||||||
minimum: string;
|
minimum: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user