diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 07de37f3..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve. Write in English, please -title: "[BUG]" -labels: bug -assignees: "" ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: - -1. Go to '...' -2. Click on '....' -3. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Additional context** - -- OS: [Windows 11/Linux Distro/Steam Deck] -- Hydra Version: -- Additional information and context of your problem: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 1a651d9f..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for Hydra. Write in English, please -title: "[REQUEST]" -labels: enhancement -assignees: "" ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/README.md b/README.md index 8947899e..62d0011c 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,13 @@ yarn make
Netflixy + + + + FerNikoMF +
+ Firdavs +
diff --git a/build/installerSidebar.bmp b/build/installerSidebar.bmp new file mode 100644 index 00000000..b5156d6d Binary files /dev/null and b/build/installerSidebar.bmp differ diff --git a/electron-builder.yml b/electron-builder.yml index 9acb2eac..06f6a5c5 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -23,6 +23,7 @@ nsis: shortcutName: ${productName} uninstallDisplayName: ${productName} createDesktopShortcut: always + oneClick: false mac: entitlementsInherit: build/entitlements.mac.plist extendInfo: diff --git a/package.json b/package.json index 1adf050f..48a8ad31 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "react-router-dom": "^6.22.3", "tough-cookie": "^4.1.3", "typeorm": "^0.3.20", + "user-agents": "^1.1.193", "windows-1251": "^3.0.4", "winston": "^3.13.0", "yaml": "^2.4.1" diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index cea1d931..4020b9e0 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", @@ -144,5 +155,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/index.ts b/src/locales/index.ts index 4653bcb5..209c4d4c 100644 --- a/src/locales/index.ts +++ b/src/locales/index.ts @@ -4,3 +4,4 @@ export { default as es } from "./es/translation.json"; export { default as fr } from "./fr/translation.json"; export { default as hu } from "./hu/translation.json"; export { default as it } from "./it/translation.json"; +export { default as ru } from "./ru/translation.json"; diff --git a/src/locales/it/translation.json b/src/locales/it/translation.json index cfdbf714..00289b63 100644 --- a/src/locales/it/translation.json +++ b/src/locales/it/translation.json @@ -17,7 +17,10 @@ "downloading": "{{title}} ({{percentage}} - Download…)", "filter": "Filtra libreria", "follow_us": "Seguici", - "home": "Home" + "home": "Home", + "discord": "Unisciti al nostro Discord", + "x": "Segui su X", + "github": "Contribuisci su GitHub" }, "header": { "search": "Cerca", @@ -77,7 +80,21 @@ "play": "Gioca", "deleting": "Eliminazione dell'installer…", "close": "Chiudi", - "playing_now": "Stai giocando adesso" + "playing_now": "Stai giocando adesso", + "change": "Aggiorna", + "repacks_modal_description": "Scegli il repack che vuoi scaricare", + "downloads_path": "Percorso dei download", + "select_folder_hint": "Per cambiare la cartella predefinita, accedi alle", + "settings": "Impostazioni", + "download_now": "Scarica ora", + "installation_instructions": "Istruzioni di installazione", + "installation_instructions_description": "Sono necessari passaggi aggiuntivi per installare questo gioco", + "online_fix_instruction": "I giochi OnlineFix richiedono una password per essere estratti. Quando richiesto, utilizza la seguente password:", + "dodi_installation_instruction": "Quando apri l'installatore di DODI, premi il tasto su della tua tastiera <0 /> per avviare il processo di installazione:", + "dont_show_it_again": "Non mostrarlo più", + "copy_to_clipboard": "Copia", + "copied_to_clipboard": "Copiato", + "got_it": "Capito" }, "activation": { "title": "Attiva Hydra", @@ -137,6 +154,9 @@ "binary_not_found_modal": { "title": "Programmi non installati", "description": "Gli eseguibili di Wine o Lutris non sono stati trovati sul tuo sistema", - "instructions": "Verifica il modo corretto di installare uno di essi sulla tua distribuzione Linux in modo che il gioco possa funzionare normalmente" + "instructions": "Verifica il modo corretto di installare uno di essi sulla tua distribuzione Linux in modo che il gioco possa funzionare normalmente" + }, + "modal": { + "close": "Pulsante Chiudi" } } diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index 0ad1b297..5f048d7a 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 jogo", + "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", @@ -144,5 +155,8 @@ "catalogue": { "next_page": "Próxima página", "previous_page": "Página anterior" + }, + "modal": { + "close": "Botão de fechar" } } diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json new file mode 100644 index 00000000..1af29e2c --- /dev/null +++ b/src/locales/ru/translation.json @@ -0,0 +1,147 @@ +{ + "home": { + "featured": "Рекомендованное", + "recently_added": "Недавно добавленное", + "trending": "Тенденции", + "surprise_me": "Удиви меня", + "no_results": "Результатов не найдено" + }, + "sidebar": { + "catalogue": "Каталог", + "downloads": "Загрузки", + "settings": "Настройки", + "my_library": "Моя библиотека", + "downloading_metadata": "{{title}} (Загрузка метаданных…)", + "checking_files": "{{title}} ({{percentage}} - Проверка файлов…)", + "paused": "{{title}} (Приостановлено)", + "downloading": "{{title}} ({{percentage}} - Загрузка…)", + "filter": "Фильтровать библиотеку", + "follow_us": "Подписывайтесь на нас", + "home": "Главная" + }, + "header": { + "search": "Поиск", + "home": "Главная", + "catalogue": "Каталог", + "downloads": "Загрузки", + "search_results": "Результаты поиска", + "settings": "Настройки" + }, + "bottom_panel": { + "no_downloads_in_progress": "Нет активных загрузок", + "downloading_metadata": "Загрузка метаданных {{title}}…", + "checking_files": "Проверка файлов {{title}}… ({{percentage}} завершено)", + "downloading": "Загрузка {{title}}… ({{percentage}} завершено) - Окончание {{eta}} - {{speed}}" + }, + "catalogue": { + "next_page": "Следующая страница", + "previous_page": "Предыдущая страница" + }, + "game_details": { + "open_download_options": "Открыть опции загрузки", + "download_options_zero": "Нет вариантов загрузки", + "download_options_one": "{{count}} вариант загрузки", + "download_options_other": "{{count}} вариантов загрузки", + "updated_at": "Обновлено {{updated_at}}", + "install": "Установить", + "resume": "Возобновить", + "pause": "Приостановить", + "cancel": "Отменить", + "remove": "Удалить", + "remove_from_list": "Удалить", + "space_left_on_disk": "{{space}} осталось на диске", + "eta": "Окончание {{eta}}", + "downloading_metadata": "Загрузка метаданных…", + "checking_files": "Проверка файлов…", + "filter": "Фильтр репаков", + "requirements": "Системные требования", + "minimum": "Минимальные", + "recommended": "Рекомендуемые", + "no_minimum_requirements": "{{title}} не предоставляет информации о минимальных требованиях", + "no_recommended_requirements": "{{title}} не предоставляет информации о рекомендуемых требованиях", + "paused_progress": "{{progress}} (Приостановлено)", + "release_date": "Выпущено в {{date}}", + "publisher": "Опубликовано {{publisher}}", + "copy_link_to_clipboard": "Скопировать ссылку", + "copied_link_to_clipboard": "Ссылка скопирована", + "hours": "часов", + "minutes": "минут", + "accuracy": "{{accuracy}}% точность", + "add_to_library": "Добавить в библиотеку", + "remove_from_library": "Удалить из библиотеки", + "no_downloads": "Нет доступных загрузок", + "play_time": "Сыграно {{amount}}", + "last_time_played": "Последний раз сыграно {{period}}", + "not_played_yet": "Вы еще не сыграли в {{title}}", + "next_suggestion": "Следующее предложение", + "play": "Играть", + "deleting": "Удаление установщика…", + "close": "Закрыть", + "playing_now": "Сейчас играет", + "change": "Изменить", + "repacks_modal_description": "Выберите репак, который хотите загрузить", + "downloads_path": "Путь загрузок", + "select_folder_hint": "Чтобы изменить папку по умолчанию, откройте", + "settings": "Настройки Hydra", + "download_now": "Загрузить сейчас" + }, + "activation": { + "title": "Активировать Hydra", + "installation_id": "ID установки:", + "enter_activation_code": "Введите ваш активационный код", + "message": "Если вы не знаете, где его запросить, то не должны иметь это.", + "activate": "Активировать", + "loading": "Загрузка…" + }, + "downloads": { + "resume": "Возобновить", + "pause": "Приостановить", + "eta": "Окончание {{eta}}", + "paused": "Приостановлено", + "verifying": "Проверка…", + "completed_at": "Завершено в {{date}}", + "completed": "Завершено", + "cancelled": "Отменено", + "download_again": "Загрузить снова", + "cancel": "Отменить", + "filter": "Фильтровать загруженные игры", + "remove": "Удалить", + "downloading_metadata": "Загрузка метаданных…", + "checking_files": "Проверка файлов…", + "starting_download": "Начало загрузки…", + "deleting": "Удаление установщика…", + "delete": "Удалить установщик", + "remove_from_list": "Удалить", + "delete_modal_title": "Вы уверены?", + "delete_modal_description": "Это удалит все установочные файлы с вашего компьютера", + "install": "Установить" + }, + "settings": { + "downloads_path": "Путь загрузок", + "change": "Изменить путь", + "notifications": "Уведомления", + "enable_download_notifications": "По завершении загрузки", + "enable_repack_list_notifications": "При добавлении нового репака", + "telemetry": "Телеметрия", + "telemetry_description": "Включить анонимную статистику использования" + }, + "notifications": { + "download_complete": "Загрузка завершена", + "game_ready_to_install": "{{title}} готова к установке", + "repack_list_updated": "Список репаков обновлен", + "repack_count_one": "{{count}} репак добавлен", + "repack_count_other": "{{count}} репаков добавлено" + }, + "system_tray": { + "open": "Открыть Hydra", + "quit": "Выйти" + }, + "game_card": { + "no_downloads": "Нет доступных загрузок" + }, + "binary_not_found_modal": { + "title": "Программы не установлены", + "description": "Исполняемые файлы Wine или Lutris не найдены на вашей системе", + "instructions": "Узнайте правильный способ установить любой из них на вашем дистрибутиве Linux, чтобы игра могла нормально работать" + } +} diff --git a/src/main/constants.ts b/src/main/constants.ts index 1a87c27b..9e0377a0 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 = [ @@ -34,7 +33,8 @@ export const months = [ "Dec", ]; -export const defaultDownloadsPath = path.join(os.homedir(), "downloads"); + +export const defaultDownloadsPath = app.getPath("downloads"); export const databasePath = path.join( app.getPath("appData"), 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/main/services/how-long-to-beat.ts b/src/main/services/how-long-to-beat.ts index 76d53c6d..ba5c98c1 100644 --- a/src/main/services/how-long-to-beat.ts +++ b/src/main/services/how-long-to-beat.ts @@ -34,6 +34,21 @@ export const searchHowLongToBeat = async (gameName: string) => { return response.data as HowLongToBeatSearchResponse; }; +const parseListItems = ($lis: Element[]) => { + return $lis.map(($li) => { + const title = $li.querySelector("h4")?.textContent; + const [, accuracyClassName] = Array.from(($li as HTMLElement).classList); + + const accuracy = accuracyClassName.split("time_").at(1); + + return { + title: title ?? "", + duration: $li.querySelector("h5")?.textContent ?? "", + accuracy: accuracy ?? "", + }; + }); +}; + export const getHowLongToBeatGame = async ( id: string ): Promise => { @@ -43,18 +58,16 @@ export const getHowLongToBeatGame = async ( const { document } = window; const $ul = document.querySelector(".shadow_shadow ul"); + if (!$ul) return []; + const $lis = Array.from($ul.children); - return $lis.map(($li) => { - const title = $li.querySelector("h4").textContent; - const [, accuracyClassName] = Array.from(($li as HTMLElement).classList); + const [$firstLi] = $lis; - const accuracy = accuracyClassName.split("time_").at(1); + if ($firstLi.tagName === "DIV") { + const $pcData = $lis.find(($li) => $li.textContent?.includes("PC")); + return parseListItems(Array.from($pcData?.querySelectorAll("li") ?? [])); + } - return { - title, - duration: $li.querySelector("h5").textContent, - accuracy, - }; - }); + return parseListItems($lis); }; diff --git a/src/main/services/repack-tracker/helpers.ts b/src/main/services/repack-tracker/helpers.ts index fd2dd7c5..1d4e8653 100644 --- a/src/main/services/repack-tracker/helpers.ts +++ b/src/main/services/repack-tracker/helpers.ts @@ -1,3 +1,5 @@ +import UserAgent from "user-agents"; + import type { Repack } from "@main/entity"; import { repackRepository } from "@main/repository"; @@ -8,7 +10,13 @@ export const savePage = async (repacks: QueryDeepPartialEntity[]) => repacks.map((repack) => repackRepository.insert(repack).catch(() => {})) ); -export const requestWebPage = async (url: string) => - fetch(url, { +export const requestWebPage = async (url: string) => { + const userAgent = new UserAgent(); + + return fetch(url, { method: "GET", + headers: { + "User-Agent": userAgent.toString(), + }, }).then((response) => response.text()); +}; 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/backdrop/backdrop.css.ts b/src/renderer/src/components/backdrop/backdrop.css.ts new file mode 100644 index 00000000..0a7b61bb --- /dev/null +++ b/src/renderer/src/components/backdrop/backdrop.css.ts @@ -0,0 +1,47 @@ +import { keyframes } from "@vanilla-extract/css"; +import { recipe } from "@vanilla-extract/recipes"; +import { SPACING_UNIT } from "../../theme.css"; + +export const backdropFadeIn = keyframes({ + "0%": { backdropFilter: "blur(0px)", backgroundColor: "rgba(0, 0, 0, 0.5)" }, + "100%": { + backdropFilter: "blur(2px)", + backgroundColor: "rgba(0, 0, 0, 0.7)", + }, +}); + +export const backdropFadeOut = keyframes({ + "0%": { backdropFilter: "blur(2px)", backgroundColor: "rgba(0, 0, 0, 0.7)" }, + "100%": { + backdropFilter: "blur(0px)", + backgroundColor: "rgba(0, 0, 0, 0)", + }, +}); + +export const backdrop = recipe({ + base: { + animationName: backdropFadeIn, + animationDuration: "0.4s", + backgroundColor: "rgba(0, 0, 0, 0.7)", + position: "absolute", + width: "100%", + height: "100%", + display: "flex", + justifyContent: "center", + alignItems: "center", + zIndex: 1, + top: 0, + padding: `${SPACING_UNIT * 3}px`, + backdropFilter: "blur(2px)", + transition: "all ease 0.2s", + }, + variants: { + closing: { + true: { + animationName: backdropFadeOut, + backdropFilter: "blur(0px)", + backgroundColor: "rgba(0, 0, 0, 0)", + }, + }, + }, +}); diff --git a/src/renderer/src/components/backdrop/backdrop.tsx b/src/renderer/src/components/backdrop/backdrop.tsx new file mode 100644 index 00000000..5852d59d --- /dev/null +++ b/src/renderer/src/components/backdrop/backdrop.tsx @@ -0,0 +1,12 @@ +import * as styles from "./backdrop.css"; + +export interface BackdropProps { + isClosing?: boolean; + children: React.ReactNode; +} + +export function Backdrop({ isClosing = false, children }: BackdropProps) { + return ( +
{children}
+ ); +} 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 d6e4732d..54f856b9 100644 --- a/src/renderer/src/components/modal/modal.css.ts +++ b/src/renderer/src/components/modal/modal.css.ts @@ -2,22 +2,6 @@ import { keyframes, style } from "@vanilla-extract/css"; import { recipe } from "@vanilla-extract/recipes"; import { SPACING_UNIT, vars } from "../../theme.css"; -export const backdropFadeIn = keyframes({ - "0%": { backdropFilter: "blur(0px)", backgroundColor: "rgba(0, 0, 0, 0.5)" }, - "100%": { - backdropFilter: "blur(2px)", - backgroundColor: "rgba(0, 0, 0, 0.7)", - }, -}); - -export const backdropFadeOut = keyframes({ - "0%": { backdropFilter: "blur(2px)", backgroundColor: "rgba(0, 0, 0, 0.7)" }, - "100%": { - backdropFilter: "blur(0px)", - backgroundColor: "rgba(0, 0, 0, 0)", - }, -}); - export const modalSlideIn = keyframes({ "0%": { opacity: 0 }, "100%": { @@ -32,34 +16,6 @@ export const modalSlideOut = keyframes({ }, }); -export const backdrop = recipe({ - base: { - animationName: backdropFadeIn, - animationDuration: "0.4s", - backgroundColor: "rgba(0, 0, 0, 0.7)", - position: "absolute", - width: "100%", - height: "100%", - display: "flex", - justifyContent: "center", - alignItems: "center", - zIndex: 1, - top: 0, - padding: `${SPACING_UNIT * 3}px`, - backdropFilter: "blur(2px)", - transition: "all ease 0.2s", - }, - variants: { - closing: { - true: { - animationName: backdropFadeOut, - backdropFilter: "blur(0px)", - backgroundColor: "rgba(0, 0, 0, 0)", - }, - }, - }, -}); - export const modal = recipe({ base: { animationName: modalSlideIn, @@ -69,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", @@ -94,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 b8b4e7ef..79308c1e 100644 --- a/src/renderer/src/components/modal/modal.tsx +++ b/src/renderer/src/components/modal/modal.tsx @@ -3,13 +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; } @@ -22,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(); @@ -81,14 +83,10 @@ export function Modal({ return () => {}; }, [handleCloseClick, visible]); - useEffect(() => { - dispatch(toggleDragging(visible)); - }, [dispatch, visible]); - if (!visible) return null; return createPortal( -
+

{title}

-

{description}

+ {description &&

{description}

}
{children}
-
, + , document.body ); } 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 fe8e65d7..0cf36566 100644 --- a/src/renderer/src/components/sidebar/sidebar.tsx +++ b/src/renderer/src/components/sidebar/sidebar.tsx @@ -17,21 +17,6 @@ import XLogo from "@renderer/assets/x-icon.svg?react"; import * as styles from "./sidebar.css"; import { GameStatus } from "@globals"; -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; @@ -50,6 +35,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(); @@ -241,6 +244,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() {
- @@ -136,12 +149,17 @@ export function HeroPanelActions({ if (game?.status === GameStatus.Paused) { return ( <> - @@ -157,6 +175,7 @@ export function HeroPanelActions({ onClick={openGameInstaller} theme="outline" disabled={deleting || isGamePlaying} + className={styles.heroPanelAction} > {t("install")} @@ -165,7 +184,12 @@ export function HeroPanelActions({ )} {isGamePlaying ? ( - ) : ( @@ -173,6 +197,7 @@ export function HeroPanelActions({ onClick={openGame} theme="outline" disabled={deleting || isGamePlaying} + className={styles.heroPanelAction} > {t("play")} @@ -184,13 +209,19 @@ export function HeroPanelActions({ if (game?.status === GameStatus.Cancelled) { return ( <> - @@ -202,7 +233,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 ecbb32b1..2da90284 100644 --- a/src/renderer/src/pages/game-details/hero-panel.tsx +++ b/src/renderer/src/pages/game-details/hero/hero-panel.tsx @@ -6,13 +6,14 @@ 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 { GameStatus } from "@globals"; +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; @@ -68,6 +69,8 @@ export function HeroPanel({ clearInterval(interval); }; } + + return () => {}; }, [game?.lastTimePlayed, updateLastTimePlayed]); const finalDownloadSize = useMemo(() => { @@ -83,7 +86,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..80370a96 --- /dev/null +++ b/src/renderer/src/pages/game-details/installation-guides/dodi-installation-guide.tsx @@ -0,0 +1,75 @@ +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..ffa07c45 --- /dev/null +++ b/src/renderer/src/pages/game-details/installation-guides/online-fix-installation-guide.tsx @@ -0,0 +1,104 @@ +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/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", diff --git a/yarn.lock b/yarn.lock index 4bb0476c..b76df8be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4311,6 +4311,21 @@ lodash-es@^4.17.21: resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz" integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz" + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + +lodash.difference@^4.5.0: + version "4.5.0" + resolved "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz" + integrity sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA== + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz" + integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== + lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz" @@ -6041,6 +6056,13 @@ use-sync-external-store@^1.0.0: resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz" integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== +user-agents@^1.1.193: + version "1.1.193" + resolved "https://registry.yarnpkg.com/user-agents/-/user-agents-1.1.193.tgz#44858e607e8a6550603cd23a173b7bd1658dd638" + integrity sha512-NKJzgR2UoVu09WmHkwlvM6+WuqJzpGCi/CukqC/ohOKCWJV5NuqZJecnox7eSWPQ2FhihuM/qTt/EQKHyrCuJw== + dependencies: + lodash.clonedeep "^4.5.0" + utf8-byte-length@^1.0.1: version "1.0.4" resolved "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz"