hydra/src/renderer/src/pages/game-details/game-details-content.tsx
Chubby Granny Chaser 55a92fd68a
docs: moving readme
2024-09-27 23:19:39 +01:00

174 lines
4.8 KiB
TypeScript

import { useContext, useEffect, useRef, useState } from "react";
import { average } from "color.js";
import Color from "color";
import { HeroPanel } from "./hero";
import { DescriptionHeader } from "./description-header/description-header";
import { GallerySlider } from "./gallery-slider/gallery-slider";
import { Sidebar } from "./sidebar/sidebar";
import * as styles from "./game-details.css";
import { useTranslation } from "react-i18next";
import { cloudSyncContext, gameDetailsContext } from "@renderer/context";
import { steamUrlBuilder } from "@shared";
import Lottie from "lottie-react";
import downloadingAnimation from "@renderer/assets/lottie/cloud.json";
import { useUserDetails } from "@renderer/hooks";
const HERO_ANIMATION_THRESHOLD = 25;
export function GameDetailsContent() {
const heroRef = useRef<HTMLDivElement | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
const [isHeaderStuck, setIsHeaderStuck] = useState(false);
const { t } = useTranslation("game_details");
const {
objectID,
shopDetails,
game,
gameColor,
setGameColor,
hasNSFWContentBlocked,
} = useContext(gameDetailsContext);
const { userDetails } = useUserDetails();
const { supportsCloudSync, setShowCloudSyncModal } =
useContext(cloudSyncContext);
const [backdropOpactiy, setBackdropOpacity] = useState(1);
const handleHeroLoad = async () => {
const output = await average(steamUrlBuilder.libraryHero(objectID!), {
amount: 1,
format: "hex",
});
const backgroundColor = output
? (new Color(output).darken(0.7).toString() as string)
: "";
setGameColor(backgroundColor);
};
useEffect(() => {
setBackdropOpacity(1);
}, [objectID]);
const onScroll: React.UIEventHandler<HTMLElement> = (event) => {
const heroHeight = heroRef.current?.clientHeight ?? styles.HERO_HEIGHT;
const scrollY = (event.target as HTMLDivElement).scrollTop;
const opacity = Math.max(
0,
1 - scrollY / (heroHeight - HERO_ANIMATION_THRESHOLD)
);
if (scrollY >= heroHeight && !isHeaderStuck) {
setIsHeaderStuck(true);
}
if (scrollY <= heroHeight && isHeaderStuck) {
setIsHeaderStuck(false);
}
setBackdropOpacity(opacity);
};
const handleCloudSaveButtonClick = () => {
if (!userDetails) {
window.electron.openAuthWindow();
return;
}
setShowCloudSyncModal(true);
};
return (
<div className={styles.wrapper({ blurredContent: hasNSFWContentBlocked })}>
<img
src={steamUrlBuilder.libraryHero(objectID!)}
className={styles.heroImage}
alt={game?.title}
onLoad={handleHeroLoad}
/>
<section
ref={containerRef}
onScroll={onScroll}
className={styles.container}
>
<div ref={heroRef} className={styles.hero}>
<div
style={{
backgroundColor: gameColor,
flex: 1,
opacity: Math.min(1, 1 - backdropOpactiy),
}}
/>
<div
className={styles.heroLogoBackdrop}
style={{ opacity: backdropOpactiy }}
>
<div className={styles.heroContent}>
<img
src={steamUrlBuilder.logo(objectID!)}
className={styles.gameLogo}
alt={game?.title}
/>
{supportsCloudSync && (
<button
type="button"
className={styles.cloudSyncButton}
onClick={handleCloudSaveButtonClick}
>
<div
style={{
width: 16 + 4,
height: 16,
display: "flex",
alignItems: "center",
justifyContent: "center",
position: "relative",
}}
>
<Lottie
animationData={downloadingAnimation}
loop
autoplay
style={{ width: 26, position: "absolute", top: -3 }}
/>
</div>
{t("cloud_save")}
</button>
)}
</div>
</div>
</div>
<HeroPanel isHeaderStuck={isHeaderStuck} />
<div className={styles.descriptionContainer}>
<div className={styles.descriptionContent}>
<DescriptionHeader />
<GallerySlider />
<div
dangerouslySetInnerHTML={{
__html: shopDetails?.about_the_game ?? t("no_shop_details"),
}}
className={styles.description}
/>
</div>
<Sidebar />
</div>
</section>
</div>
);
}