diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 03f00a0d..03dd4b6d 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -17,7 +17,10 @@ "downloading": "{{title}} ({{percentage}} - Downloading…)", "filter": "Filter library", "follow_us": "Follow us", - "home": "Home" + "home": "Home", + "discord": "Join our Discord", + "x": "Follow on X", + "github": "Contribute on GitHub" }, "header": { "search": "Search", @@ -82,8 +85,16 @@ "repacks_modal_description": "Choose the repack you want to download", "downloads_path": "Downloads path", "select_folder_hint": "To change the default folder, access the", - "settings": "Hydra settings", - "download_now": "Download now" + "settings": "Settings", + "download_now": "Download now", + "installation_instructions": "Installation Instructions", + "installation_instructions_description": "Additional steps are required to install this game", + "online_fix_instruction": "OnlineFix games requires a password to be extracted. When required, use the following password:", + "dodi_installation_instruction": "When you open DODI installer, press your keyboard up key <0 /> to start the installation process:", + "dont_show_it_again": "Don't show it again", + "copy_to_clipboard": "Copy", + "copied_to_clipboard": "Copied", + "got_it": "Got it" }, "activation": { "title": "Activate Hydra", @@ -143,5 +154,8 @@ "title": "Programs not installed", "description": "Wine or Lutris executables were not found on your system", "instructions": "Check the correct way to install any of them on your Linux distro so that the game can run normally" + }, + "modal": { + "close": "Close button" } } diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index 270e0451..df27fa34 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -17,7 +17,10 @@ "downloading": "{{title}} ({{percentage}} - Baixando…)", "filter": "Filtrar biblioteca", "home": "Início", - "follow_us": "Acompanhe-nos" + "follow_us": "Acompanhe-nos", + "discord": "Entre no nosso Discord", + "x": "Siga-nos no X", + "github": "Contribua no GitHub" }, "header": { "search": "Buscar", @@ -79,7 +82,15 @@ "downloads_path": "Diretório do download", "select_folder_hint": "Para trocar a pasta padrão, acesse as ", "settings": "Configurações do Hydra", - "download_now": "Baixe agora" + "download_now": "Baixe agora", + "installation_instructions": "Instruções de Instalação", + "installation_instructions_description": "Passos adicionais são necessários para instalar esse jogos", + "online_fix_instruction": "Jogos OnlineFix precisam de uma senha para serem extraídos. Quando solicitado, utilize a seguinte senha:", + "dodi_installation_instruction": "Quando o instalador do DODI for aberto, pressione a seta para cima <0 /> do teclado para iniciar o processo de instalação:", + "dont_show_it_again": "Não mostrar novamente", + "copy_to_clipboard": "Copiar", + "copied_to_clipboard": "Copiado", + "got_it": "Entendi" }, "activation": { "title": "Ativação", @@ -143,5 +154,8 @@ "catalogue": { "next_page": "Próxima página", "previous_page": "Página anterior" + }, + "modal": { + "close": "Botão de fechar" } } diff --git a/src/main/constants.ts b/src/main/constants.ts index 92c4da04..1697279f 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -1,5 +1,4 @@ import { app } from "electron"; -import os from "node:os"; import path from "node:path"; export const repackersOn1337x = [ diff --git a/src/main/events/helpers/search-games.ts b/src/main/events/helpers/search-games.ts index f931b1d9..c4209add 100644 --- a/src/main/events/helpers/search-games.ts +++ b/src/main/events/helpers/search-games.ts @@ -8,7 +8,7 @@ import { stateManager } from "@main/state-manager"; const { Index } = flexSearch; const repacksIndex = new Index(); -const steamGamesIndex = new Index({ tokenize: "reverse" }); +const steamGamesIndex = new Index(); const repacks = stateManager.getValue("repacks"); const steamGames = stateManager.getValue("steamGames"); diff --git a/src/renderer/index.html b/src/renderer/index.html index 1917de45..3147179e 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -6,10 +6,10 @@ Hydra - +
diff --git a/src/renderer/src/app.css.ts b/src/renderer/src/app.css.ts index fc104adc..2a55f033 100644 --- a/src/renderer/src/app.css.ts +++ b/src/renderer/src/app.css.ts @@ -26,6 +26,7 @@ globalStyle("body", { overflow: "hidden", userSelect: "none", fontFamily: "'Fira Mono', monospace", + fontSize: vars.size.bodyFontSize, background: vars.color.background, color: vars.color.bodyText, margin: "0", @@ -36,13 +37,16 @@ globalStyle("button", { backgroundColor: "transparent", border: "none", fontFamily: "inherit", - fontSize: vars.size.bodyFontSize, }); globalStyle("h1, h2, h3, h4, h5, h6, p", { margin: 0, }); +globalStyle("p", { + lineHeight: "20px", +}); + globalStyle("#root, main", { display: "flex", }); @@ -103,5 +107,5 @@ export const titleBar = style({ padding: `0 ${SPACING_UNIT * 2}px`, WebkitAppRegion: "drag", zIndex: "2", - borderBottom: `1px solid ${vars.color.borderColor}`, + borderBottom: `1px solid ${vars.color.border}`, } as ComplexStyleRule); diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 6461c0d0..d5331336 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -18,11 +18,16 @@ import { clearSearch, setUserPreferences, setRepackersFriendlyNames, + toggleDraggingDisabled, } from "@renderer/features"; document.body.classList.add(themeClass); -export function App({ children }: any) { +export interface AppProps { + children: React.ReactNode; +} + +export function App({ children }: AppProps) { const contentRef = useRef(null); const { updateLibrary } = useLibrary(); @@ -34,6 +39,9 @@ export function App({ children }: any) { const location = useLocation(); const search = useAppSelector((state) => state.search.value); + const draggingDisabled = useAppSelector( + (state) => state.window.draggingDisabled + ); useEffect(() => { Promise.all([ @@ -93,6 +101,17 @@ export function App({ children }: any) { if (contentRef.current) contentRef.current.scrollTop = 0; }, [location.pathname, location.search]); + useEffect(() => { + new MutationObserver(() => { + const modal = document.body.querySelector("[role=modal]"); + + dispatch(toggleDraggingDisabled(Boolean(modal))); + }).observe(document.body, { + attributes: false, + childList: true, + }); + }, [dispatch, draggingDisabled]); + return ( <> {window.electron.platform === "win32" && ( diff --git a/src/renderer/src/components/bottom-panel/bottom-panel.css.ts b/src/renderer/src/components/bottom-panel/bottom-panel.css.ts index f251d1c0..f339e0d5 100644 --- a/src/renderer/src/components/bottom-panel/bottom-panel.css.ts +++ b/src/renderer/src/components/bottom-panel/bottom-panel.css.ts @@ -3,13 +3,12 @@ import { SPACING_UNIT, vars } from "../../theme.css"; export const bottomPanel = style({ width: "100%", - borderTop: `solid 1px ${vars.color.borderColor}`, + borderTop: `solid 1px ${vars.color.border}`, padding: `${SPACING_UNIT / 2}px ${SPACING_UNIT * 2}px`, display: "flex", alignItems: "center", transition: "all ease 0.2s", justifyContent: "space-between", - fontSize: vars.size.bodyFontSize, zIndex: "1", }); diff --git a/src/renderer/src/components/button/button.css.ts b/src/renderer/src/components/button/button.css.ts index 18866bc0..2cc19776 100644 --- a/src/renderer/src/components/button/button.css.ts +++ b/src/renderer/src/components/button/button.css.ts @@ -3,7 +3,7 @@ import { SPACING_UNIT, vars } from "../../theme.css"; const base = style({ padding: `${SPACING_UNIT}px ${SPACING_UNIT * 2}px`, - backgroundColor: "#c0c1c7", + backgroundColor: vars.color.muted, borderRadius: "8px", border: "solid 1px transparent", transition: "all ease 0.2s", @@ -35,8 +35,8 @@ export const button = styleVariants({ base, { backgroundColor: "transparent", - border: "solid 1px #c0c1c7", - color: "#c0c1c7", + border: `solid 1px ${vars.color.border}`, + color: vars.color.muted, ":hover": { backgroundColor: "rgba(255, 255, 255, 0.1)", }, diff --git a/src/renderer/src/components/checkbox-field/checkbox-field.css.ts b/src/renderer/src/components/checkbox-field/checkbox-field.css.ts index 2b7cb77c..1aa7ee0e 100644 --- a/src/renderer/src/components/checkbox-field/checkbox-field.css.ts +++ b/src/renderer/src/components/checkbox-field/checkbox-field.css.ts @@ -19,7 +19,7 @@ export const checkbox = style({ alignItems: "center", position: "relative", transition: "all ease 0.2s", - border: `solid 1px ${vars.color.borderColor}`, + border: `solid 1px ${vars.color.border}`, ":hover": { borderColor: "rgba(255, 255, 255, 0.5)", }, diff --git a/src/renderer/src/components/game-card/game-card.css.ts b/src/renderer/src/components/game-card/game-card.css.ts index f8d835fb..9f2f0654 100644 --- a/src/renderer/src/components/game-card/game-card.css.ts +++ b/src/renderer/src/components/game-card/game-card.css.ts @@ -10,7 +10,7 @@ export const card = recipe({ overflow: "hidden", borderRadius: "4px", transition: "all ease 0.2s", - border: `solid 1px ${vars.color.borderColor}`, + border: `solid 1px ${vars.color.border}`, cursor: "pointer", zIndex: "1", ":active": { @@ -103,7 +103,7 @@ export const specifics = style({ export const specificsItem = style({ gap: `${SPACING_UNIT}px`, display: "flex", - color: "#c0c1c7", + color: vars.color.muted, fontSize: "12px", alignItems: "flex-end", }); @@ -112,7 +112,7 @@ export const titleContainer = style({ display: "flex", alignItems: "center", gap: `${SPACING_UNIT}px`, - color: "#c0c1c7", + color: vars.color.muted, }); export const shopIcon = style({ diff --git a/src/renderer/src/components/header/header.css.ts b/src/renderer/src/components/header/header.css.ts index eb95dc6e..705b533e 100644 --- a/src/renderer/src/components/header/header.css.ts +++ b/src/renderer/src/components/header/header.css.ts @@ -29,8 +29,8 @@ export const header = recipe({ WebkitAppRegion: "drag", width: "100%", padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 3}px`, - color: "#c0c1c7", - borderBottom: `solid 1px ${vars.color.borderColor}`, + color: vars.color.muted, + borderBottom: `solid 1px ${vars.color.border}`, backgroundColor: vars.color.darkBackground, } as ComplexStyleRule, variants: { @@ -55,7 +55,7 @@ export const search = recipe({ width: "200px", alignItems: "center", borderRadius: "8px", - border: `solid 1px ${vars.color.borderColor}`, + border: `solid 1px ${vars.color.border}`, height: "40px", WebkitAppRegion: "no-drag", } as ComplexStyleRule, @@ -83,7 +83,6 @@ export const searchInput = style({ color: "#DADBE1", cursor: "default", fontFamily: "inherit", - fontSize: vars.size.bodyFontSize, textOverflow: "ellipsis", ":focus": { cursor: "text", diff --git a/src/renderer/src/components/hero/hero.css.ts b/src/renderer/src/components/hero/hero.css.ts index 3c9ec81c..261a22ac 100644 --- a/src/renderer/src/components/hero/hero.css.ts +++ b/src/renderer/src/components/hero/hero.css.ts @@ -11,7 +11,7 @@ export const hero = style({ overflow: "hidden", boxShadow: "0px 0px 15px 0px #000000", cursor: "pointer", - border: `solid 1px ${vars.color.borderColor}`, + border: `solid 1px ${vars.color.border}`, zIndex: "1", }); @@ -33,7 +33,7 @@ export const heroMedia = style({ export const backdrop = style({ width: "100%", height: "100%", - background: "linear-gradient(0deg, rgba(0, 0, 0, 0.6) 25%, transparent 100%)", + background: "linear-gradient(0deg, rgba(0, 0, 0, 0.8) 25%, transparent 100%)", position: "relative", display: "flex", overflow: "hidden", @@ -41,8 +41,7 @@ export const backdrop = style({ export const description = style({ maxWidth: "700px", - fontSize: vars.size.bodyFontSize, - color: "#c0c1c7", + color: vars.color.muted, textAlign: "left", fontFamily: "'Fira Sans', sans-serif", lineHeight: "20px", diff --git a/src/renderer/src/components/hero/hero.tsx b/src/renderer/src/components/hero/hero.tsx index 9eadbd74..c3d27a3d 100644 --- a/src/renderer/src/components/hero/hero.tsx +++ b/src/renderer/src/components/hero/hero.tsx @@ -6,7 +6,7 @@ import { ShopDetails } from "@types"; import { getSteamLanguage, steamUrlBuilder } from "@renderer/helpers"; import { useTranslation } from "react-i18next"; -const FEATURED_GAME_ID = "253230"; +const FEATURED_GAME_ID = "2420110"; export function Hero() { const [featuredGameDetails, setFeaturedGameDetails] = @@ -36,7 +36,7 @@ export function Hero() { >
diff --git a/src/renderer/src/components/modal/modal.css.ts b/src/renderer/src/components/modal/modal.css.ts index 37a3fef5..54f856b9 100644 --- a/src/renderer/src/components/modal/modal.css.ts +++ b/src/renderer/src/components/modal/modal.css.ts @@ -25,7 +25,7 @@ export const modal = recipe({ maxWidth: "600px", color: vars.color.bodyText, maxHeight: "100%", - border: `solid 1px ${vars.color.borderColor}`, + border: `solid 1px ${vars.color.border}`, overflow: "hidden", display: "flex", flexDirection: "column", @@ -50,13 +50,18 @@ export const modalHeader = style({ display: "flex", gap: `${SPACING_UNIT}px`, padding: `${SPACING_UNIT * 2}px`, - borderBottom: `solid 1px ${vars.color.borderColor}`, + borderBottom: `solid 1px ${vars.color.border}`, justifyContent: "space-between", - alignItems: "flex-start", + alignItems: "center", }); export const closeModalButton = style({ cursor: "pointer", + transition: "all ease 0.2s", + alignSelf: "flex-start", + ":hover": { + opacity: "0.75", + }, }); export const closeModalButtonIcon = style({ diff --git a/src/renderer/src/components/modal/modal.tsx b/src/renderer/src/components/modal/modal.tsx index 7bfc0dcc..79308c1e 100644 --- a/src/renderer/src/components/modal/modal.tsx +++ b/src/renderer/src/components/modal/modal.tsx @@ -3,14 +3,14 @@ import { createPortal } from "react-dom"; import { XIcon } from "@primer/octicons-react"; import * as styles from "./modal.css"; -import { useAppDispatch } from "@renderer/hooks"; -import { toggleDragging } from "@renderer/features"; + import { Backdrop } from "../backdrop/backdrop"; +import { useTranslation } from "react-i18next"; export interface ModalProps { visible: boolean; title: string; - description: string; + description?: string; onClose: () => void; children: React.ReactNode; } @@ -23,9 +23,10 @@ export function Modal({ children, }: ModalProps) { const [isClosing, setIsClosing] = useState(false); - const dispatch = useAppDispatch(); const modalContentRef = useRef(null); + const { t } = useTranslation("modal"); + const handleCloseClick = useCallback(() => { setIsClosing(true); const zero = performance.now(); @@ -82,10 +83,6 @@ export function Modal({ return () => {}; }, [handleCloseClick, visible]); - useEffect(() => { - dispatch(toggleDragging(visible)); - }, [dispatch, visible]); - if (!visible) return null; return createPortal( @@ -98,13 +95,14 @@ export function Modal({

{title}

-

{description}

+ {description &&

{description}

}
diff --git a/src/renderer/src/components/sidebar/sidebar.css.ts b/src/renderer/src/components/sidebar/sidebar.css.ts index e4ff22f8..b8527645 100644 --- a/src/renderer/src/components/sidebar/sidebar.css.ts +++ b/src/renderer/src/components/sidebar/sidebar.css.ts @@ -5,11 +5,11 @@ import { SPACING_UNIT, vars } from "../../theme.css"; export const sidebar = recipe({ base: { backgroundColor: vars.color.darkBackground, - color: "#c0c1c7", + color: vars.color.muted, flexDirection: "column", display: "flex", transition: "opacity ease 0.2s", - borderRight: `solid 1px ${vars.color.borderColor}`, + borderRight: `solid 1px ${vars.color.border}`, position: "relative", }, variants: { @@ -65,7 +65,7 @@ export const menuItem = recipe({ textWrap: "nowrap", display: "flex", opacity: "0.9", - color: "#DADBE1", + color: vars.color.muted, ":hover": { opacity: "1", }, @@ -130,7 +130,7 @@ export const section = recipe({ variants: { hasBorder: { true: { - borderBottom: `solid 1px ${vars.color.borderColor}`, + borderBottom: `solid 1px ${vars.color.border}`, }, }, }, @@ -157,10 +157,10 @@ export const footerSocialsItem = style({ height: "16px", display: "flex", alignItems: "center", - transition: "all ease 0.15s", + transition: "all ease 0.2s", + cursor: "pointer", ":hover": { - opacity: 0.75, - cursor: "pointer", + opacity: "0.75", }, }); diff --git a/src/renderer/src/components/sidebar/sidebar.tsx b/src/renderer/src/components/sidebar/sidebar.tsx index 5cdb2572..9906affb 100644 --- a/src/renderer/src/components/sidebar/sidebar.tsx +++ b/src/renderer/src/components/sidebar/sidebar.tsx @@ -16,21 +16,6 @@ import XLogo from "@renderer/assets/x-icon.svg?react"; import * as styles from "./sidebar.css"; -const socials = [ - { - url: "https://discord.gg/hydralauncher", - icon: , - }, - { - url: "https://twitter.com/hydralauncher", - icon: , - }, - { - url: "https://github.com/hydralauncher/hydra", - icon: , - }, -]; - const SIDEBAR_MIN_WIDTH = 200; const SIDEBAR_INITIAL_WIDTH = 250; const SIDEBAR_MAX_WIDTH = 450; @@ -49,6 +34,24 @@ export function Sidebar() { initialSidebarWidth ? Number(initialSidebarWidth) : SIDEBAR_INITIAL_WIDTH ); + const socials = [ + { + url: "https://discord.gg/hydralauncher", + icon: , + label: t("discord"), + }, + { + url: "https://twitter.com/hydralauncher", + icon: , + label: t("x"), + }, + { + url: "https://github.com/hydralauncher/hydra", + icon: , + label: t("github"), + }, + ]; + const location = useLocation(); const { game: gameDownloading, progress } = useDownload(); @@ -243,6 +246,8 @@ export function Sidebar() { key={item.url} className={styles.footerSocialsItem} onClick={() => window.electron.openExternal(item.url)} + title={item.label} + aria-label={item.label} > {item.icon} diff --git a/src/renderer/src/components/text-field/text-field.css.ts b/src/renderer/src/components/text-field/text-field.css.ts index d38230ef..f5469fa1 100644 --- a/src/renderer/src/components/text-field/text-field.css.ts +++ b/src/renderer/src/components/text-field/text-field.css.ts @@ -9,7 +9,7 @@ export const textField = recipe({ width: "100%", alignItems: "center", borderRadius: "8px", - border: `solid 1px ${vars.color.borderColor}`, + border: `solid 1px ${vars.color.border}`, height: "40px", minHeight: "40px", }, @@ -44,7 +44,6 @@ export const textFieldInput = style({ color: "#DADBE1", cursor: "default", fontFamily: "inherit", - fontSize: vars.size.bodyFontSize, textOverflow: "ellipsis", padding: `${SPACING_UNIT}px`, ":focus": { diff --git a/src/renderer/src/components/text-field/text-field.tsx b/src/renderer/src/components/text-field/text-field.tsx index 3b86e290..b26b961d 100644 --- a/src/renderer/src/components/text-field/text-field.tsx +++ b/src/renderer/src/components/text-field/text-field.tsx @@ -9,11 +9,16 @@ export interface TextFieldProps > { theme?: NonNullable>["theme"]; label?: string; + textFieldProps?: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLDivElement + >; } export function TextField({ theme = "primary", label, + textFieldProps, ...props }: TextFieldProps) { const [isFocused, setIsFocused] = useState(false); @@ -27,7 +32,10 @@ export function TextField({ )} -
+
) => { + toggleDraggingDisabled: (state, action: PayloadAction) => { state.draggingDisabled = action.payload; }, setHeaderTitle: (state, action: PayloadAction) => { @@ -24,4 +24,4 @@ export const windowSlice = createSlice({ }, }); -export const { toggleDragging, setHeaderTitle } = windowSlice.actions; +export const { toggleDraggingDisabled, setHeaderTitle } = windowSlice.actions; diff --git a/src/renderer/src/helpers.ts b/src/renderer/src/helpers.ts index ae100f62..89da2bca 100644 --- a/src/renderer/src/helpers.ts +++ b/src/renderer/src/helpers.ts @@ -21,5 +21,8 @@ export const getSteamLanguage = (language: string) => { if (language.startsWith("pt")) return "brazilian"; if (language.startsWith("es")) return "spanish"; if (language.startsWith("fr")) return "french"; + if (language.startsWith("ru")) return "russian"; + if (language.startsWith("it")) return "italian"; + if (language.startsWith("hu")) return "hungarian"; return "english"; }; diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index 8dc5fc56..a809e246 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -6,7 +6,7 @@ import type { CatalogueEntry } from "@types"; import { clearSearch } from "@renderer/features"; import { useAppDispatch } from "@renderer/hooks"; -import { vars } from "../../theme.css"; +import { SPACING_UNIT, vars } from "../../theme.css"; import { useEffect, useRef, useState } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; import * as styles from "../home/home.css"; @@ -67,12 +67,12 @@ export function Catalogue() {
- @@ -135,12 +148,17 @@ export function HeroPanelActions({ if (game?.status === "paused") { return ( <> - @@ -156,6 +174,7 @@ export function HeroPanelActions({ onClick={openGameInstaller} theme="outline" disabled={deleting || isGamePlaying} + className={styles.heroPanelAction} > {t("install")} @@ -164,7 +183,12 @@ export function HeroPanelActions({ )} {isGamePlaying ? ( - ) : ( @@ -172,6 +196,7 @@ export function HeroPanelActions({ onClick={openGame} theme="outline" disabled={deleting || isGamePlaying} + className={styles.heroPanelAction} > {t("play")} @@ -183,13 +208,19 @@ export function HeroPanelActions({ if (game?.status === "cancelled") { return ( <> - @@ -201,7 +232,11 @@ export function HeroPanelActions({ return ( <> {toggleGameOnLibraryButton} - diff --git a/src/renderer/src/pages/game-details/hero-panel.css.ts b/src/renderer/src/pages/game-details/hero/hero-panel.css.ts similarity index 81% rename from src/renderer/src/pages/game-details/hero-panel.css.ts rename to src/renderer/src/pages/game-details/hero/hero-panel.css.ts index 6824f9a4..08ffff8a 100644 --- a/src/renderer/src/pages/game-details/hero-panel.css.ts +++ b/src/renderer/src/pages/game-details/hero/hero-panel.css.ts @@ -1,5 +1,5 @@ import { style } from "@vanilla-extract/css"; -import { SPACING_UNIT, vars } from "../../theme.css"; +import { SPACING_UNIT, vars } from "../../../theme.css"; export const panel = style({ width: "100%", @@ -9,7 +9,8 @@ export const panel = style({ alignItems: "center", justifyContent: "space-between", transition: "all ease 0.2s", - borderBottom: `solid 1px ${vars.color.borderColor}`, + borderBottom: `solid 1px ${vars.color.border}`, + color: "#8e919b", boxShadow: "0px 0px 15px 0px #000000", }); @@ -17,7 +18,6 @@ export const content = style({ display: "flex", flexDirection: "column", gap: `${SPACING_UNIT}px`, - fontSize: vars.size.bodyFontSize, }); export const actions = style({ diff --git a/src/renderer/src/pages/game-details/hero-panel.tsx b/src/renderer/src/pages/game-details/hero/hero-panel.tsx similarity index 97% rename from src/renderer/src/pages/game-details/hero-panel.tsx rename to src/renderer/src/pages/game-details/hero/hero-panel.tsx index 09a04970..f45f8e57 100644 --- a/src/renderer/src/pages/game-details/hero-panel.tsx +++ b/src/renderer/src/pages/game-details/hero/hero-panel.tsx @@ -6,12 +6,13 @@ import { useDownload } from "@renderer/hooks"; import type { Game, ShopDetails } from "@types"; import { formatDownloadProgress } from "@renderer/helpers"; -import { BinaryNotFoundModal } from "../shared-modals/binary-not-found-modal"; -import * as styles from "./hero-panel.css"; import { useDate } from "@renderer/hooks/use-date"; import { formatBytes } from "@renderer/utils"; import { HeroPanelActions } from "./hero-panel-actions"; +import { BinaryNotFoundModal } from "../../shared-modals/binary-not-found-modal"; +import * as styles from "./hero-panel.css"; + export interface HeroPanelProps { game: Game | null; gameDetails: ShopDetails | null; @@ -67,6 +68,8 @@ export function HeroPanel({ clearInterval(interval); }; } + + return () => {}; }, [game?.lastTimePlayed, updateLastTimePlayed]); const finalDownloadSize = useMemo(() => { @@ -82,7 +85,7 @@ export function HeroPanel({ const getInfo = () => { if (!gameDetails) return null; - if (isGameDeleting(game?.id)) { + if (isGameDeleting(game?.id ?? -1)) { return

{t("deleting")}

; } diff --git a/src/renderer/src/pages/game-details/hero/index.ts b/src/renderer/src/pages/game-details/hero/index.ts new file mode 100644 index 00000000..b4cc12ed --- /dev/null +++ b/src/renderer/src/pages/game-details/hero/index.ts @@ -0,0 +1 @@ +export * from "./hero-panel"; diff --git a/src/renderer/src/pages/game-details/how-long-to-beat-section.tsx b/src/renderer/src/pages/game-details/how-long-to-beat-section.tsx index c46afbf6..dad0b928 100644 --- a/src/renderer/src/pages/game-details/how-long-to-beat-section.tsx +++ b/src/renderer/src/pages/game-details/how-long-to-beat-section.tsx @@ -1,6 +1,6 @@ import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; -import type { HowLongToBeatCategory } from "@types"; import { useTranslation } from "react-i18next"; +import type { HowLongToBeatCategory } from "@types"; import { vars } from "../../theme.css"; import * as styles from "./game-details.css"; diff --git a/src/renderer/src/pages/game-details/installation-guides/constants.ts b/src/renderer/src/pages/game-details/installation-guides/constants.ts new file mode 100644 index 00000000..e20f7714 --- /dev/null +++ b/src/renderer/src/pages/game-details/installation-guides/constants.ts @@ -0,0 +1,3 @@ +export const DONT_SHOW_ONLINE_FIX_INSTRUCTIONS_KEY = + "dontShowOnlineFixInstructions"; +export const DONT_SHOW_DODI_INSTRUCTIONS_KEY = "dontShowDodiInstructions"; diff --git a/src/renderer/src/pages/game-details/installation-guides/dodi-installation-guide.css.ts b/src/renderer/src/pages/game-details/installation-guides/dodi-installation-guide.css.ts new file mode 100644 index 00000000..d95add53 --- /dev/null +++ b/src/renderer/src/pages/game-details/installation-guides/dodi-installation-guide.css.ts @@ -0,0 +1,31 @@ +import { vars } from "../../../theme.css"; +import { keyframes, style } from "@vanilla-extract/css"; + +export const slideIn = keyframes({ + "0%": { transform: "translateY(0)" }, + "40%": { transform: "translateY(0)" }, + "70%": { transform: "translateY(-100%)" }, + "100%": { transform: "translateY(-100%)" }, +}); + +export const windowContainer = style({ + width: "250px", + height: "150px", + alignSelf: "center", + borderRadius: "2px", + overflow: "hidden", + border: `solid 1px ${vars.color.border}`, +}); + +export const windowContent = style({ + backgroundColor: vars.color.muted, + height: "90%", + animationName: slideIn, + animationDuration: "3s", + animationIterationCount: "infinite", + animationTimingFunction: "ease-out", + display: "flex", + alignItems: "center", + justifyContent: "center", + color: "#1c1c1c", +}); diff --git a/src/renderer/src/pages/game-details/installation-guides/dodi-installation-guide.tsx b/src/renderer/src/pages/game-details/installation-guides/dodi-installation-guide.tsx new file mode 100644 index 00000000..b838b90a --- /dev/null +++ b/src/renderer/src/pages/game-details/installation-guides/dodi-installation-guide.tsx @@ -0,0 +1,74 @@ +import { useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; + +import { Button, CheckboxField, Modal } from "@renderer/components"; +import { SPACING_UNIT } from "@renderer/theme.css"; + +import * as styles from "./dodi-installation-guide.css"; +import { ArrowUpIcon } from "@primer/octicons-react"; +import { DONT_SHOW_DODI_INSTRUCTIONS_KEY } from "./constants"; + +export interface DODIInstallationGuideProps { + windowColor: string; + visible: boolean; + onClose: () => void; +} + +export function DODIInstallationGuide({ + windowColor, + visible, + onClose, +}: DODIInstallationGuideProps) { + const { t } = useTranslation("game_details"); + + const [dontShowAgain, setDontShowAgain] = useState(true); + + const handleClose = () => { + if (dontShowAgain) { + window.localStorage.setItem(DONT_SHOW_DODI_INSTRUCTIONS_KEY, "1"); + onClose(); + } + }; + + return ( + +
+

+ + + +

+ +
+
+ +
+
+ + setDontShowAgain(!dontShowAgain)} + checked={dontShowAgain} + /> + + +
+
+ ); +} diff --git a/src/renderer/src/pages/game-details/installation-guides/index.ts b/src/renderer/src/pages/game-details/installation-guides/index.ts new file mode 100644 index 00000000..ff5b129e --- /dev/null +++ b/src/renderer/src/pages/game-details/installation-guides/index.ts @@ -0,0 +1,3 @@ +export * from "./online-fix-installation-guide"; +export * from "./dodi-installation-guide"; +export * from "./constants"; diff --git a/src/renderer/src/pages/game-details/installation-guides/online-fix-installation-guide.css.ts b/src/renderer/src/pages/game-details/installation-guides/online-fix-installation-guide.css.ts new file mode 100644 index 00000000..891f11be --- /dev/null +++ b/src/renderer/src/pages/game-details/installation-guides/online-fix-installation-guide.css.ts @@ -0,0 +1,7 @@ +import { SPACING_UNIT } from "../../../theme.css"; +import { style } from "@vanilla-extract/css"; + +export const passwordField = style({ + display: "flex", + gap: `${SPACING_UNIT}px`, +}); diff --git a/src/renderer/src/pages/game-details/installation-guides/online-fix-installation-guide.tsx b/src/renderer/src/pages/game-details/installation-guides/online-fix-installation-guide.tsx new file mode 100644 index 00000000..93ab65ad --- /dev/null +++ b/src/renderer/src/pages/game-details/installation-guides/online-fix-installation-guide.tsx @@ -0,0 +1,103 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; + +import { Button, CheckboxField, Modal, TextField } from "@renderer/components"; +import { SPACING_UNIT } from "@renderer/theme.css"; + +import * as styles from "./online-fix-installation-guide.css"; +import { CopyIcon } from "@primer/octicons-react"; +import { DONT_SHOW_ONLINE_FIX_INSTRUCTIONS_KEY } from "./constants"; + +const ONLINE_FIX_PASSWORD = "online-fix.me"; + +export interface OnlineFixInstallationGuideProps { + visible: boolean; + onClose: () => void; +} + +export function OnlineFixInstallationGuide({ + visible, + onClose, +}: OnlineFixInstallationGuideProps) { + const [clipboardLocked, setClipboardLocked] = useState(false); + const { t } = useTranslation("game_details"); + + const [dontShowAgain, setDontShowAgain] = useState(true); + + const handleCopyToClipboard = () => { + setClipboardLocked(true); + + navigator.clipboard.writeText(ONLINE_FIX_PASSWORD); + + const zero = performance.now(); + + requestAnimationFrame(function holdLock(time) { + if (time - zero <= 3000) { + requestAnimationFrame(holdLock); + } else { + setClipboardLocked(false); + } + }); + }; + + const handleClose = () => { + if (dontShowAgain) { + window.localStorage.setItem(DONT_SHOW_ONLINE_FIX_INSTRUCTIONS_KEY, "1"); + onClose(); + } + }; + + return ( + +
+

{t("online_fix_instruction")}

+
+ + + +
+ + setDontShowAgain(!dontShowAgain)} + checked={dontShowAgain} + /> + + +
+
+ ); +} diff --git a/src/renderer/src/pages/game-details/online-fix-installation-guide.tsx b/src/renderer/src/pages/game-details/online-fix-installation-guide.tsx deleted file mode 100644 index 768440f1..00000000 --- a/src/renderer/src/pages/game-details/online-fix-installation-guide.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Button, CheckboxField, Modal, TextField } from "@renderer/components"; -import { createPortal } from "react-dom"; - -export function OnlineFixInstallationGuide() { - return ( - -
-

- When asked for an extraction password for OnlineFix repacks, use the - following one: -

- - - - - - -
- ); -} diff --git a/src/renderer/src/pages/game-details/repacks-modal.tsx b/src/renderer/src/pages/game-details/repacks-modal.tsx index fe684a85..f6b7d7d4 100644 --- a/src/renderer/src/pages/game-details/repacks-modal.tsx +++ b/src/renderer/src/pages/game-details/repacks-modal.tsx @@ -14,22 +14,19 @@ import { SelectFolderModal } from "./select-folder-modal"; export interface RepacksModalProps { visible: boolean; gameDetails: ShopDetails; - showSelectFolderModal: boolean; - setShowSelectFolderModal: (value: boolean) => void; - startDownload: (repackId: number, downloadPath: string) => Promise; + startDownload: (repack: GameRepack, downloadPath: string) => Promise; onClose: () => void; } export function RepacksModal({ visible, gameDetails, - showSelectFolderModal, - setShowSelectFolderModal, startDownload, onClose, }: RepacksModalProps) { const [filteredRepacks, setFilteredRepacks] = useState([]); const [repack, setRepack] = useState(null); + const [showSelectFolderModal, setShowSelectFolderModal] = useState(false); const repackersFriendlyNames = useAppSelector( (state) => state.repackersFriendlyNames.value @@ -57,12 +54,7 @@ export function RepacksModal({ }; return ( - + <> setShowSelectFolderModal(false)} @@ -70,26 +62,34 @@ export function RepacksModal({ startDownload={startDownload} repack={repack} /> -
- -
-
- {filteredRepacks.map((repack) => ( - - ))} -
-
+ +
+ +
+ +
+ {filteredRepacks.map((repack) => ( + + ))} +
+
+ ); } diff --git a/src/renderer/src/pages/game-details/select-folder-modal.css.tsx b/src/renderer/src/pages/game-details/select-folder-modal.css.tsx index 42629ffe..d9d0d540 100644 --- a/src/renderer/src/pages/game-details/select-folder-modal.css.tsx +++ b/src/renderer/src/pages/game-details/select-folder-modal.css.tsx @@ -10,10 +10,18 @@ export const container = style({ export const downloadsPathField = style({ display: "flex", - gap: `${SPACING_UNIT * 2}px`, + gap: `${SPACING_UNIT}px`, }); export const hintText = style({ fontSize: "12px", color: vars.color.bodyText, }); + +export const settingsLink = style({ + textDecoration: "none", + color: "#C0C1C7", + ":hover": { + textDecoration: "underline", + }, +}); diff --git a/src/renderer/src/pages/game-details/select-folder-modal.tsx b/src/renderer/src/pages/game-details/select-folder-modal.tsx index af6e4691..266b48c5 100644 --- a/src/renderer/src/pages/game-details/select-folder-modal.tsx +++ b/src/renderer/src/pages/game-details/select-folder-modal.tsx @@ -7,12 +7,13 @@ import { formatBytes } from "@renderer/utils"; import { DiskSpace } from "check-disk-space"; import { Link } from "react-router-dom"; import * as styles from "./select-folder-modal.css"; +import { DownloadIcon } from "@primer/octicons-react"; export interface SelectFolderModalProps { visible: boolean; gameDetails: ShopDetails; onClose: () => void; - startDownload: (repackId: number, downloadPath: string) => Promise; + startDownload: (repack: GameRepack, downloadPath: string) => Promise; repack: GameRepack | null; } @@ -63,8 +64,10 @@ export function SelectFolderModal({ const handleStartClick = () => { if (repack) { setDownloadStarting(true); - startDownload(repack.id, selectedPath).finally(() => { + + startDownload(repack, selectedPath).finally(() => { setDownloadStarting(false); + onClose(); }); } }; @@ -98,17 +101,12 @@ export function SelectFolderModal({

{t("select_folder_hint")}{" "} - + {t("settings")}

diff --git a/src/renderer/src/pages/settings/settings.css.ts b/src/renderer/src/pages/settings/settings.css.ts index 506b9574..7b1dd60d 100644 --- a/src/renderer/src/pages/settings/settings.css.ts +++ b/src/renderer/src/pages/settings/settings.css.ts @@ -12,7 +12,7 @@ export const content = style({ width: "100%", height: "100%", padding: `${SPACING_UNIT * 3}px`, - border: `solid 1px ${vars.color.borderColor}`, + border: `solid 1px ${vars.color.border}`, boxShadow: "0px 0px 15px 0px #000000", borderRadius: "8px", gap: `${SPACING_UNIT * 2}px`, @@ -22,5 +22,5 @@ export const content = style({ export const downloadsPathField = style({ display: "flex", - gap: `${SPACING_UNIT * 2}px`, + gap: `${SPACING_UNIT}px`, }); diff --git a/src/renderer/src/theme.css.ts b/src/renderer/src/theme.css.ts index b11f1acb..dfadc89d 100644 --- a/src/renderer/src/theme.css.ts +++ b/src/renderer/src/theme.css.ts @@ -6,8 +6,9 @@ export const [themeClass, vars] = createTheme({ color: { background: "#1c1c1c", darkBackground: "#151515", + muted: "#c0c1c7", bodyText: "#8e919b", - borderColor: "rgba(255, 255, 255, 0.1)", + border: "#424244", }, opacity: { disabled: "0.5",