diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index d920cfdb..7b54b889 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -98,7 +98,11 @@ "got_it": "Got it", "no_shop_details": "Could not retrieve shop details.", "download_options": "Download options", - "download_path": "Download path" + "download_path": "Download path", + "previous_screenshot": "Previous screenshot", + "next_screenshot": "Next screenshot", + "screenshot": "Screenshot {{number}}", + "open_screenshot": "Open screenshot {{number}}" }, "activation": { "title": "Activate Hydra", diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index ab33bfe5..57ec0470 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -94,7 +94,11 @@ "got_it": "Entendi", "no_shop_details": "Não foi possível obter os detalhes da loja.", "download_options": "Opções de download", - "download_path": "Diretório de download" + "download_path": "Diretório de download", + "previous_screenshot": "Captura de tela anterior", + "next_screenshot": "Próxima captura de tela", + "screenshot": "Captura de tela {{number}}", + "open_screenshot": "Ver captura de tela {{number}}" }, "activation": { "title": "Ativação", diff --git a/src/main/events/catalogue/get-game-shop-details.ts b/src/main/events/catalogue/get-game-shop-details.ts index f53bf398..bbc3b08a 100644 --- a/src/main/events/catalogue/get-game-shop-details.ts +++ b/src/main/events/catalogue/get-game-shop-details.ts @@ -1,4 +1,4 @@ -import { gameShopCacheRepository } from "@main/repository"; +import { gameShopCacheRepository, steamGameRepository } from "@main/repository"; import { getSteamAppDetails } from "@main/services"; import type { ShopDetails, GameShop, SteamAppDetails } from "@types"; @@ -9,18 +9,18 @@ const getLocalizedSteamAppDetails = ( objectID: string, language: string ): Promise => { - const englishAppDetails = getSteamAppDetails(objectID, "english"); - - if (language === "english") return englishAppDetails; + if (language === "english") { + return getSteamAppDetails(objectID, language); + } return Promise.all([ - englishAppDetails, + steamGameRepository.findOne({ where: { id: Number(objectID) } }), getSteamAppDetails(objectID, language), - ]).then(([appDetails, localizedAppDetails]) => { - if (appDetails && localizedAppDetails) { + ]).then(([steamGame, localizedAppDetails]) => { + if (steamGame && localizedAppDetails) { return { ...localizedAppDetails, - name: appDetails.name, + name: steamGame.name, }; } diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index d9b7821e..1ffc5099 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -57,6 +57,7 @@ i18n }, }) .then(() => { + i18n.changeLanguage("pt-BR"); window.electron.updateUserPreferences({ language: i18n.language }); }); diff --git a/src/renderer/src/pages/game-details/gallery-slider.css.ts b/src/renderer/src/pages/game-details/gallery-slider.css.ts index c7e0b7c2..801c3a5b 100644 --- a/src/renderer/src/pages/game-details/gallery-slider.css.ts +++ b/src/renderer/src/pages/game-details/gallery-slider.css.ts @@ -1,3 +1,4 @@ +import { recipe } from "@vanilla-extract/recipes"; import { SPACING_UNIT, vars } from "../../theme.css"; import { style } from "@vanilla-extract/css"; @@ -13,7 +14,7 @@ export const gallerySliderMedia = style({ width: "100%", height: "100%", display: "block", - flexShrink: 0, + flexShrink: "0", flexGrow: "0", transition: "translate 0.3s ease-in-out", borderRadius: "4px", @@ -54,42 +55,77 @@ export const gallerySliderPreview = style({ }, }); -export const gallerySliderMediaPreview = style({ - cursor: "pointer", - width: "20%", - height: "20%", - display: "block", - flexShrink: 0, - flexGrow: 0, - opacity: 0.3, - transition: "translate 0.3s ease-in-out, opacity 0.2s ease", - borderRadius: "4px", - border: `solid 1px ${vars.color.border}`, - ":hover": { - opacity: "1", +export const mediaPreviewButton = recipe({ + base: { + cursor: "pointer", + width: "20%", + height: "20%", + display: "block", + flexShrink: "0", + flexGrow: "0", + opacity: "0.3", + transition: "translate 0.3s ease-in-out, opacity 0.2s ease", + borderRadius: "4px", + border: `solid 1px ${vars.color.border}`, + ":hover": { + opacity: "0.8", + }, + }, + variants: { + active: { + true: { + opacity: "1", + }, + }, }, }); -export const gallerySliderMediaPreviewActive = style({ - opacity: 1, +export const mediaPreview = style({ + width: "100%", + height: "100%", + display: "flex", + flex: "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 gallerySliderButton = recipe({ + base: { + position: "absolute", + alignSelf: "center", + cursor: "pointer", + backgroundColor: "rgba(0, 0, 0, 0.4)", + transition: "all 0.2s ease-in-out", + borderRadius: "50%", + color: vars.color.muted, + width: "48px", + height: "48px", + ":hover": { + backgroundColor: "rgba(0, 0, 0, 0.6)", + }, + ":active": { + transform: "scale(0.95)", + }, + }, + variants: { + direction: { + left: { + left: "0", + marginLeft: `${SPACING_UNIT}px`, + transform: `translateX(${-(48 + SPACING_UNIT)}px)`, + }, + right: { + right: "0", + marginRight: `${SPACING_UNIT}px`, + transform: `translateX(${48 + SPACING_UNIT}px)`, + }, + }, + visible: { + true: { + transform: "translateX(0)", + opacity: "1", + }, + false: { + opacity: "0", + }, + }, }, }); - -export const gallerySliderIcons = style({ - fill: vars.color.muted, - width: "2rem", - height: "2rem", -}); diff --git a/src/renderer/src/pages/game-details/gallery-slider.tsx b/src/renderer/src/pages/game-details/gallery-slider.tsx index d2b89fb3..8879b2ff 100644 --- a/src/renderer/src/pages/game-details/gallery-slider.tsx +++ b/src/renderer/src/pages/game-details/gallery-slider.tsx @@ -1,7 +1,10 @@ -import { useEffect, useRef, useState } from "react"; -import { ShopDetails, SteamMovies, SteamScreenshot } from "@types"; +import { useEffect, useMemo, useRef, useState } from "react"; import { ChevronRightIcon, ChevronLeftIcon } from "@primer/octicons-react"; + +import type { ShopDetails } from "@types"; + import * as styles from "./gallery-slider.css"; +import { useTranslation } from "react-i18next"; export interface GallerySliderProps { gameDetails: ShopDetails; @@ -9,6 +12,12 @@ export interface GallerySliderProps { export function GallerySlider({ gameDetails }: GallerySliderProps) { const scrollContainerRef = useRef(null); + const mediaContainerRef = useRef(null); + + const { t } = useTranslation("game_details"); + + const hasScreenshots = gameDetails && gameDetails.screenshots.length; + const hasMovies = gameDetails && gameDetails.movies?.length; const [mediaCount] = useState(() => { if (gameDetails.screenshots && gameDetails.movies) { @@ -23,7 +32,7 @@ export function GallerySlider({ gameDetails }: GallerySliderProps) { }); const [mediaIndex, setMediaIndex] = useState(0); - const [arrowShow, setArrowShow] = useState(false); + const [showArrows, setShowArrows] = useState(false); const showNextImage = () => { setMediaIndex((index: number) => { @@ -45,6 +54,20 @@ export function GallerySlider({ gameDetails }: GallerySliderProps) { setMediaIndex(0); }, [gameDetails]); + useEffect(() => { + if (hasMovies && mediaContainerRef.current) { + mediaContainerRef.current.childNodes.forEach((node, index) => { + if (node instanceof HTMLVideoElement) { + if (index == mediaIndex) { + node.play(); + } else { + node.pause(); + } + } + }); + } + }, [hasMovies, mediaContainerRef, mediaIndex]); + useEffect(() => { if (scrollContainerRef.current) { const container = scrollContainerRef.current; @@ -55,93 +78,107 @@ export function GallerySlider({ gameDetails }: GallerySliderProps) { } }, [gameDetails, mediaIndex, mediaCount]); - const hasScreenshots = gameDetails.screenshots.length; - const hasMovies = gameDetails.movies?.length; + const previews = useMemo(() => { + const screenshotPreviews = + gameDetails?.screenshots.map(({ id, path_thumbnail }) => ({ + id, + thumbnail: path_thumbnail, + })) ?? []; + + if (gameDetails?.movies) { + const moviePreviews = gameDetails.movies.map(({ id, thumbnail }) => ({ + id, + thumbnail, + })); + + return [...moviePreviews, ...screenshotPreviews]; + } + + return screenshotPreviews; + }, [gameDetails]); return ( <> {hasScreenshots && (
setArrowShow(true)} - onMouseLeave={() => setArrowShow(false)} + onMouseEnter={() => setShowArrows(true)} + onMouseLeave={() => setShowArrows(false)} className={styles.gallerySliderAnimationContainer} + ref={mediaContainerRef} > {gameDetails.movies && - gameDetails.movies.map((video: SteamMovies) => ( + gameDetails.movies.map((video) => ( ))} + {hasScreenshots && - gameDetails.screenshots.map( - (image: SteamScreenshot, i: number) => ( - - ) - )} - {arrowShow && ( - <> - - - - - )} -
- -
- {hasMovies && - gameDetails.movies?.map((video: SteamMovies, i: number) => ( + gameDetails.screenshots.map((image, i) => ( setMediaIndex(i)} - src={video.thumbnail} - className={`${styles.gallerySliderMediaPreview} ${mediaIndex === i ? styles.gallerySliderMediaPreviewActive : ""}`} + key={image.id} + className={styles.gallerySliderMedia} + src={image.path_full} + style={{ translate: `${-100 * mediaIndex}%` }} + alt={t("screenshot", { number: i + 1 })} /> ))} - {hasScreenshots && - gameDetails.screenshots.map( - (image: SteamScreenshot, i: number) => ( - - 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} - /> - ) - )} + + + +
+ +
+ {previews.map((media, i) => ( + + ))}
)}