From c9e99d3852ed6794cc40ebcf4b2eda1ee57d5e20 Mon Sep 17 00:00:00 2001 From: bumyy Date: Thu, 7 Nov 2024 20:23:03 -0300 Subject: [PATCH 001/291] feat: migrated to scss --- src/renderer/src/app.scss | 130 +++++++++++++ src/renderer/src/app.tsx | 2 + .../src/components/backdrop/backdrop.css.ts | 54 ------ .../src/components/backdrop/backdrop.scss | 50 +++++ .../src/components/backdrop/backdrop.tsx | 9 +- .../checkbox-field/checkbox-field.css.ts | 41 ---- .../checkbox-field/checkbox-field.scss | 39 ++++ .../checkbox-field/checkbox-field.tsx | 10 +- .../confirmation-modal.css.ts | 13 -- .../confirmation-modal.scss | 17 ++ .../confirmation-modal/confirmation-modal.tsx | 8 +- .../src/components/game-card/game-card.css.ts | 106 ---------- .../src/components/game-card/game-card.scss | 102 ++++++++++ .../src/components/game-card/game-card.tsx | 27 +-- .../components/header/auto-update-header.scss | 32 +++ .../header/auto-update-sub-header.tsx | 23 ++- .../src/components/header/header.css.ts | 182 ------------------ .../src/components/header/header.scss | 132 +++++++++++++ src/renderer/src/components/header/header.tsx | 34 ++-- src/renderer/src/components/hero/hero.css.ts | 60 ------ src/renderer/src/components/hero/hero.scss | 56 ++++++ src/renderer/src/components/hero/hero.tsx | 14 +- src/renderer/src/components/link/link.css.ts | 9 - src/renderer/src/components/link/link.scss | 7 + src/renderer/src/components/link/link.tsx | 10 +- .../src/components/modal/modal.css.ts | 78 -------- src/renderer/src/components/modal/modal.scss | 77 ++++++++ src/renderer/src/components/modal/modal.tsx | 16 +- .../select-field/select-field.css.ts | 59 ------ .../components/select-field/select-field.scss | 49 +++++ .../components/select-field/select-field.tsx | 16 +- .../components/sidebar/sidebar-profile.css.ts | 79 -------- .../components/sidebar/sidebar-profile.scss | 77 ++++++++ .../components/sidebar/sidebar-profile.tsx | 16 +- .../src/components/sidebar/sidebar.css.ts | 126 ------------ .../src/components/sidebar/sidebar.scss | 110 +++++++++++ .../src/components/sidebar/sidebar.tsx | 44 +++-- .../components/text-field/text-field.css.ts | 89 --------- .../src/components/text-field/text-field.scss | 75 ++++++++ .../src/components/text-field/text-field.tsx | 35 ++-- .../src/components/toast/toast.css.ts | 87 --------- src/renderer/src/components/toast/toast.scss | 87 +++++++++ src/renderer/src/components/toast/toast.tsx | 23 ++- .../profile-content/profile-content.css.ts | 11 +- src/renderer/src/scss/globals.scss | 5 + 45 files changed, 1214 insertions(+), 1112 deletions(-) create mode 100644 src/renderer/src/app.scss delete mode 100644 src/renderer/src/components/backdrop/backdrop.css.ts create mode 100644 src/renderer/src/components/backdrop/backdrop.scss delete mode 100644 src/renderer/src/components/checkbox-field/checkbox-field.css.ts create mode 100644 src/renderer/src/components/checkbox-field/checkbox-field.scss delete mode 100644 src/renderer/src/components/confirmation-modal/confirmation-modal.css.ts create mode 100644 src/renderer/src/components/confirmation-modal/confirmation-modal.scss delete mode 100644 src/renderer/src/components/game-card/game-card.css.ts create mode 100644 src/renderer/src/components/game-card/game-card.scss create mode 100644 src/renderer/src/components/header/auto-update-header.scss delete mode 100644 src/renderer/src/components/header/header.css.ts create mode 100644 src/renderer/src/components/header/header.scss delete mode 100644 src/renderer/src/components/hero/hero.css.ts create mode 100644 src/renderer/src/components/hero/hero.scss delete mode 100644 src/renderer/src/components/link/link.css.ts create mode 100644 src/renderer/src/components/link/link.scss delete mode 100644 src/renderer/src/components/modal/modal.css.ts create mode 100644 src/renderer/src/components/modal/modal.scss delete mode 100644 src/renderer/src/components/select-field/select-field.css.ts create mode 100644 src/renderer/src/components/select-field/select-field.scss delete mode 100644 src/renderer/src/components/sidebar/sidebar-profile.css.ts create mode 100644 src/renderer/src/components/sidebar/sidebar-profile.scss delete mode 100644 src/renderer/src/components/sidebar/sidebar.css.ts create mode 100644 src/renderer/src/components/sidebar/sidebar.scss delete mode 100644 src/renderer/src/components/text-field/text-field.css.ts create mode 100644 src/renderer/src/components/text-field/text-field.scss delete mode 100644 src/renderer/src/components/toast/toast.css.ts create mode 100644 src/renderer/src/components/toast/toast.scss diff --git a/src/renderer/src/app.scss b/src/renderer/src/app.scss new file mode 100644 index 00000000..2f383d04 --- /dev/null +++ b/src/renderer/src/app.scss @@ -0,0 +1,130 @@ +@use "./scss/globals.scss"; + +* { + box-sizing: border-box; +} + +::-webkit-scrollbar { + width: 9px; + background-color: globals.$dark-background-color; +} + +::-webkit-scrollbar-track { + background-color: rgba(255, 255, 255, 0.03); +} + +::-webkit-scrollbar-thumb { + background-color: rgba(255, 255, 255, 0.08); + border-radius: 24px; +} + +::-webkit-scrollbar-thumb:hover { + background-color: rgba(255, 255, 255, 0.16); +} + +html, +body, +#root, +main { + height: 100%; +} + +body { + overflow: hidden; + user-select: none; + font-family: + Noto Sans, + sans-serif; + font-size: globals.$body-font-size; + color: globals.$body-color; + margin: 0; +} + +button { + padding: 0; + background-color: transparent; + border: none; + font-family: inherit; +} + +h1, +h2, +h3, +h4, +h5, +h6, +p { + margin: 0; +} + +p { + line-height: 20px; +} + +#root, +main { + display: flex; +} + +#root { + flex-direction: column; +} + +main { + overflow: hidden; +} + +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +label { + font-size: globals.$body-font-size; +} + +img { + -webkit-user-drag: none; +} + +progress[value] { + -webkit-appearance: none; +} + +.container { + width: 100%; + height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + container-name: globals.$app-container; + container-type: inline-size; + + &__content { + overflow-y: auto; + align-items: center; + display: flex; + flex-direction: column; + position: relative; + height: 100%; + background: linear-gradient( + 0deg, + globals.$dark-background-color 50%, + globals.$background-color 100% + ); + } +} + +.title-bar { + display: flex; + width: 100%; + height: 35px; + min-height: 35px; + background-color: globals.$dark-background-color; + align-items: center; + padding: 0 calc(globals.$spacing-unit * 2); + -webkit-app-region: drag; + z-index: 4; + border-bottom: 1px solid globals.$border-color; +} diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 7c572a56..ce184474 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -2,6 +2,8 @@ import { useCallback, useContext, useEffect, useRef } from "react"; import { Sidebar, BottomPanel, Header, Toast } from "@renderer/components"; +import "./app.scss"; + import { useAppDispatch, useAppSelector, diff --git a/src/renderer/src/components/backdrop/backdrop.css.ts b/src/renderer/src/components/backdrop/backdrop.css.ts deleted file mode 100644 index 1ccfe12f..00000000 --- a/src/renderer/src/components/backdrop/backdrop.css.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { keyframes } 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 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: vars.zIndex.backdrop, - 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)", - }, - }, - windows: { - true: { - // SPACING_UNIT * 3 + title bar spacing - paddingTop: `${SPACING_UNIT * 3 + 35}px`, - }, - }, - }, -}); diff --git a/src/renderer/src/components/backdrop/backdrop.scss b/src/renderer/src/components/backdrop/backdrop.scss new file mode 100644 index 00000000..d62ff9a9 --- /dev/null +++ b/src/renderer/src/components/backdrop/backdrop.scss @@ -0,0 +1,50 @@ +@use "../../scss/globals.scss"; + +.backdrop { + animation-name: backdrop-fade-in; + animation-duration: 0.4s; + background-color: rgba(0, 0, 0, 0.7); + position: absolute; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + z-index: globals.$backdrop-z-index; + top: 0; + padding: calc(globals.$spacing-unit * 3); + backdrop-filter: blur(2px); + transition: all ease 0.2s; + + &--closing { + animation-name: backdrop-fade-out; + backdrop-filter: blur(0px); + background-color: rgba(0, 0, 0, 0); + } + + &--windows { + padding-top: calc(#{globals.$spacing-unit * 3} + 35); + } +} + +@keyframes backdrop-fade-in { + 0% { + backdrop-filter: blur(0px); + background-color: rgba(0, 0, 0, 0.5); + } + 100% { + backdrop-filter: blur(2px); + background-color: rgba(0, 0, 0, 0.7); + } +} + +@keyframes backdrop-fade-out { + 0% { + backdrop-filter: blur(2px); + background-color: rgba(0, 0, 0, 0.7); + } + 100% { + backdrop-filter: blur(0px); + background-color: rgba(0, 0, 0, 0); + } +} diff --git a/src/renderer/src/components/backdrop/backdrop.tsx b/src/renderer/src/components/backdrop/backdrop.tsx index f498e664..e62d42ee 100644 --- a/src/renderer/src/components/backdrop/backdrop.tsx +++ b/src/renderer/src/components/backdrop/backdrop.tsx @@ -1,4 +1,5 @@ -import * as styles from "./backdrop.css"; +import "./backdrop.scss"; +import cn from "classnames"; export interface BackdropProps { isClosing?: boolean; @@ -8,9 +9,9 @@ export interface BackdropProps { export function Backdrop({ isClosing = false, children }: BackdropProps) { return (
{children} diff --git a/src/renderer/src/components/checkbox-field/checkbox-field.css.ts b/src/renderer/src/components/checkbox-field/checkbox-field.css.ts deleted file mode 100644 index 606b226a..00000000 --- a/src/renderer/src/components/checkbox-field/checkbox-field.css.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { style } from "@vanilla-extract/css"; - -import { SPACING_UNIT, vars } from "../../theme.css"; - -export const checkboxField = style({ - display: "flex", - flexDirection: "row", - alignItems: "center", - gap: `${SPACING_UNIT}px`, - cursor: "pointer", -}); - -export const checkbox = style({ - width: "20px", - height: "20px", - borderRadius: "4px", - backgroundColor: vars.color.darkBackground, - display: "flex", - justifyContent: "center", - alignItems: "center", - position: "relative", - transition: "all ease 0.2s", - border: `solid 1px ${vars.color.border}`, - ":hover": { - borderColor: "rgba(255, 255, 255, 0.5)", - }, -}); - -export const checkboxInput = style({ - width: "100%", - height: "100%", - position: "absolute", - margin: "0", - padding: "0", - opacity: "0", - cursor: "pointer", -}); - -export const checkboxLabel = style({ - cursor: "pointer", -}); diff --git a/src/renderer/src/components/checkbox-field/checkbox-field.scss b/src/renderer/src/components/checkbox-field/checkbox-field.scss new file mode 100644 index 00000000..06235687 --- /dev/null +++ b/src/renderer/src/components/checkbox-field/checkbox-field.scss @@ -0,0 +1,39 @@ +@use "../../scss/globals.scss"; + +.checkbox-field { + display: flex; + flex-direction: row; + align-items: center; + gap: globals.$spacing-unit; + cursor: pointer; + + &__checkbox { + width: 20px; + height: 20px; + border-radius: 4px; + background-color: globals.$dark-background-color; + display: flex; + justify-content: center; + align-items: center; + position: relative; + transition: all ease 0.2s; + border: solid 1px globals.$border-color; + &:hover { + border-color: rgba(255, 255, 255, 0.5); + } + } + + &__input { + width: 100%; + height: 100%; + position: absolute; + margin: 0; + padding: 0; + opacity: 0; + cursor: pointer; + } + + &__label { + cursor: pointer; + } +} diff --git a/src/renderer/src/components/checkbox-field/checkbox-field.tsx b/src/renderer/src/components/checkbox-field/checkbox-field.tsx index bb81a910..3e80f0aa 100644 --- a/src/renderer/src/components/checkbox-field/checkbox-field.tsx +++ b/src/renderer/src/components/checkbox-field/checkbox-field.tsx @@ -1,6 +1,6 @@ import { useId } from "react"; -import * as styles from "./checkbox-field.css"; import { CheckIcon } from "@primer/octicons-react"; +import "./checkbox-field.scss"; export interface CheckboxFieldProps extends React.DetailedHTMLProps< @@ -14,17 +14,17 @@ export function CheckboxField({ label, ...props }: CheckboxFieldProps) { const id = useId(); return ( -
-
+
+
{props.checked && }
-
diff --git a/src/renderer/src/components/confirmation-modal/confirmation-modal.css.ts b/src/renderer/src/components/confirmation-modal/confirmation-modal.css.ts deleted file mode 100644 index a9aec403..00000000 --- a/src/renderer/src/components/confirmation-modal/confirmation-modal.css.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { SPACING_UNIT } from "../../theme.css"; -import { style } from "@vanilla-extract/css"; - -export const actions = style({ - display: "flex", - alignSelf: "flex-end", - gap: `${SPACING_UNIT * 2}px`, -}); - -export const descriptionText = style({ - fontSize: "16px", - lineHeight: "24px", -}); diff --git a/src/renderer/src/components/confirmation-modal/confirmation-modal.scss b/src/renderer/src/components/confirmation-modal/confirmation-modal.scss new file mode 100644 index 00000000..428818c4 --- /dev/null +++ b/src/renderer/src/components/confirmation-modal/confirmation-modal.scss @@ -0,0 +1,17 @@ +@use "../../scss/globals.scss"; + +.confirmation-modal { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 2); + + &__actions { + display: flex; + align-self: flex-end; + gap: calc(globals.$spacing-unit * 2); + } + &__description { + font-size: 16px; + line-height: 24px; + } +} diff --git a/src/renderer/src/components/confirmation-modal/confirmation-modal.tsx b/src/renderer/src/components/confirmation-modal/confirmation-modal.tsx index 31929c60..eaf3526a 100644 --- a/src/renderer/src/components/confirmation-modal/confirmation-modal.tsx +++ b/src/renderer/src/components/confirmation-modal/confirmation-modal.tsx @@ -1,7 +1,7 @@ import { Button } from "../button/button"; import { Modal, type ModalProps } from "../modal/modal"; -import * as styles from "./confirmation-modal.css"; +import "./confirmation-modal.scss"; export interface ConfirmationModalProps extends Omit { confirmButtonLabel: string; @@ -31,10 +31,10 @@ export function ConfirmationModal({ return ( -
-

{descriptionText}

+
+

{descriptionText}

-
+
diff --git a/src/renderer/src/components/game-card/game-card.css.ts b/src/renderer/src/components/game-card/game-card.css.ts deleted file mode 100644 index c810130d..00000000 --- a/src/renderer/src/components/game-card/game-card.css.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { style } from "@vanilla-extract/css"; - -import { SPACING_UNIT, vars } from "../../theme.css"; - -export const card = style({ - width: "100%", - height: "180px", - boxShadow: "0px 0px 15px 0px #000000", - overflow: "hidden", - borderRadius: "4px", - transition: "all ease 0.2s", - border: `solid 1px ${vars.color.border}`, - cursor: "pointer", - zIndex: "1", - ":active": { - opacity: vars.opacity.active, - }, -}); - -export const backdrop = style({ - background: "linear-gradient(0deg, rgba(0, 0, 0, 0.7) 50%, transparent 100%)", - width: "100%", - height: "100%", - display: "flex", - justifyContent: "flex-end", - flexDirection: "column", - position: "relative", -}); - -export const cover = style({ - width: "100%", - height: "100%", - objectFit: "cover", - objectPosition: "center", - position: "absolute", - zIndex: "-1", - transition: "all ease 0.2s", - selectors: { - [`${card}:hover &`]: { - transform: "scale(1.05)", - }, - }, -}); - -export const content = style({ - color: "#DADBE1", - padding: `${SPACING_UNIT}px ${SPACING_UNIT * 2}px`, - display: "flex", - alignItems: "flex-start", - gap: `${SPACING_UNIT}px`, - flexDirection: "column", - transition: "all ease 0.2s", - transform: "translateY(24px)", - selectors: { - [`${card}:hover &`]: { - transform: "translateY(0px)", - }, - }, -}); - -export const title = style({ - fontSize: "16px", - fontWeight: "bold", - textAlign: "left", -}); - -export const downloadOptions = style({ - display: "flex", - margin: "0", - padding: "0", - gap: `${SPACING_UNIT}px`, - flexWrap: "wrap", - listStyle: "none", -}); - -export const specifics = style({ - display: "flex", - gap: `${SPACING_UNIT * 2}px`, - justifyContent: "center", -}); - -export const specificsItem = style({ - gap: `${SPACING_UNIT}px`, - display: "flex", - color: vars.color.muted, - fontSize: "12px", - alignItems: "flex-end", -}); - -export const titleContainer = style({ - display: "flex", - alignItems: "center", - gap: `${SPACING_UNIT}px`, - color: vars.color.muted, -}); - -export const shopIcon = style({ - width: "20px", - height: "20px", - minWidth: "20px", -}); - -export const noDownloadsLabel = style({ - color: vars.color.body, - fontWeight: "bold", -}); diff --git a/src/renderer/src/components/game-card/game-card.scss b/src/renderer/src/components/game-card/game-card.scss new file mode 100644 index 00000000..ee4a22b1 --- /dev/null +++ b/src/renderer/src/components/game-card/game-card.scss @@ -0,0 +1,102 @@ +@use "../../scss/globals.scss"; + +.game-card { + width: 100%; + height: 180px; + box-shadow: 0px 0px 15px 0px #000000; + overflow: hidden; + border-radius: 4px; + transition: all ease 0.2s; + border: solid 1px globals.$border-color; + cursor: pointer; + z-index: 1; + + &:active { + opacity: globals.$active-opacity; + } + + &__backdrop { + background: linear-gradient(0deg, rgba(0, 0, 0, 0.7) 50%, transparent 100%); + width: 100%; + height: 100%; + display: flex; + justify-content: flex-end; + flex-direction: column; + position: relative; + } + + &__cover { + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + position: absolute; + z-index: -1; + transition: all ease 0.2s; + } + + &__content { + color: #dadbe1; + padding: globals.$spacing-unit calc(globals.$spacing-unit * 2); + display: flex; + align-items: flex-start; + gap: globals.$spacing-unit; + flex-direction: column; + transition: all ease 0.2s; + transform: translateY(24px); + } + + &__title { + font-size: 16px; + font-weight: bold; + text-align: left; + } + + &__download-options { + display: flex; + margin: 0; + padding: 0; + gap: globals.$spacing-unit; + flex-wrap: wrap; + list-style: none; + } + + &__specifics { + display: flex; + gap: calc(globals.$spacing-unit * 2); + justify-content: center; + } + + &__specifics-item { + gap: globals.$spacing-unit; + display: flex; + color: globals.$muted-color; + font-size: 12px; + align-items: flex-end; + } + + &__title-container { + display: flex; + align-items: center; + gap: globals.$spacing-unit; + color: globals.$muted-color; + } + + &__shop-icon { + width: 20px; + height: 20px; + min-width: 20px; + } + + &__no-download-label { + color: globals.$body-color; + font-weight: bold; + } + + &:hover &__cover { + transform: scale(1.05); + } + &:hover &__content { + transform: translateY(0px); + } +} diff --git a/src/renderer/src/components/game-card/game-card.tsx b/src/renderer/src/components/game-card/game-card.tsx index 869cb2d6..62290704 100644 --- a/src/renderer/src/components/game-card/game-card.tsx +++ b/src/renderer/src/components/game-card/game-card.tsx @@ -3,7 +3,8 @@ import type { CatalogueEntry, GameRepack, GameStats } from "@types"; import SteamLogo from "@renderer/assets/steam-logo.svg?react"; -import * as styles from "./game-card.css"; +import "./game-card.scss"; + import { useTranslation } from "react-i18next"; import { Badge } from "../badge/badge"; import { useCallback, useContext, useEffect, useState } from "react"; @@ -19,7 +20,7 @@ export interface GameCardProps } const shopIcon = { - steam: , + steam: , }; export function GameCard({ game, ...props }: GameCardProps) { @@ -56,25 +57,25 @@ export function GameCard({ game, ...props }: GameCardProps) { diff --git a/src/renderer/src/components/header/header.css.ts b/src/renderer/src/components/header/header.css.ts deleted file mode 100644 index 12855986..00000000 --- a/src/renderer/src/components/header/header.css.ts +++ /dev/null @@ -1,182 +0,0 @@ -import type { ComplexStyleRule } from "@vanilla-extract/css"; -import { keyframes, style } from "@vanilla-extract/css"; -import { recipe } from "@vanilla-extract/recipes"; - -import { SPACING_UNIT, vars } from "../../theme.css"; - -export const slideIn = keyframes({ - "0%": { transform: "translateX(20px)", opacity: "0" }, - "100%": { - transform: "translateX(0)", - opacity: "1", - }, -}); - -export const slideOut = keyframes({ - "0%": { transform: "translateX(0px)", opacity: "1" }, - "100%": { - transform: "translateX(20px)", - opacity: "0", - }, -}); - -export const header = recipe({ - base: { - display: "flex", - justifyContent: "space-between", - alignItems: "center", - gap: `${SPACING_UNIT * 2}px`, - WebkitAppRegion: "drag", - width: "100%", - padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 3}px`, - color: vars.color.muted, - borderBottom: `solid 1px ${vars.color.border}`, - backgroundColor: vars.color.darkBackground, - } as ComplexStyleRule, - variants: { - draggingDisabled: { - true: { - WebkitAppRegion: "no-drag", - } as ComplexStyleRule, - }, - isWindows: { - true: { - WebkitAppRegion: "no-drag", - } as ComplexStyleRule, - }, - }, -}); - -export const search = recipe({ - base: { - backgroundColor: vars.color.background, - display: "inline-flex", - transition: "all ease 0.2s", - width: "200px", - alignItems: "center", - borderRadius: "8px", - border: `solid 1px ${vars.color.border}`, - height: "40px", - WebkitAppRegion: "no-drag", - } as ComplexStyleRule, - variants: { - focused: { - true: { - width: "250px", - borderColor: "#DADBE1", - }, - false: { - ":hover": { - borderColor: "rgba(255, 255, 255, 0.5)", - }, - }, - }, - }, -}); - -export const searchInput = style({ - backgroundColor: "transparent", - border: "none", - width: "100%", - height: "100%", - outline: "none", - color: "#DADBE1", - cursor: "default", - fontFamily: "inherit", - textOverflow: "ellipsis", - ":focus": { - cursor: "text", - }, -}); - -export const actionButton = style({ - color: "inherit", - cursor: "pointer", - transition: "all ease 0.2s", - padding: `${SPACING_UNIT}px`, - ":hover": { - color: "#DADBE1", - }, -}); - -export const section = style({ - display: "flex", - alignItems: "center", - gap: `${SPACING_UNIT * 2}px`, - height: "100%", - overflow: "hidden", -}); - -export const backButton = recipe({ - base: { - color: vars.color.body, - cursor: "pointer", - WebkitAppRegion: "no-drag", - position: "absolute", - transition: "transform ease 0.2s", - animationDuration: "0.2s", - width: "16px", - height: "16px", - display: "flex", - alignItems: "center", - } as ComplexStyleRule, - variants: { - enabled: { - true: { - animationName: slideIn, - }, - false: { - opacity: "0", - pointerEvents: "none", - animationName: slideOut, - }, - }, - }, -}); - -export const title = recipe({ - base: { - transition: "all ease 0.2s", - overflow: "hidden", - textOverflow: "ellipsis", - width: "100%", - }, - variants: { - hasBackButton: { - true: { - transform: "translateX(28px)", - width: "calc(100% - 28px)", - }, - }, - }, -}); - -export const subheader = style({ - borderBottom: `solid 1px ${vars.color.border}`, - padding: `${SPACING_UNIT / 2}px ${SPACING_UNIT * 3}px`, -}); - -export const newVersionButton = style({ - display: "flex", - alignItems: "center", - justifyContent: "center", - gap: `${SPACING_UNIT}px`, - color: vars.color.body, - fontSize: "12px", - ":hover": { - textDecoration: "underline", - cursor: "pointer", - }, -}); - -export const newVersionLink = style({ - display: "flex", - alignItems: "center", - gap: `${SPACING_UNIT}px`, - color: "#8e919b", - fontSize: "12px", -}); - -export const newVersionIcon = style({ - color: vars.color.success, -}); diff --git a/src/renderer/src/components/header/header.scss b/src/renderer/src/components/header/header.scss new file mode 100644 index 00000000..065aed8d --- /dev/null +++ b/src/renderer/src/components/header/header.scss @@ -0,0 +1,132 @@ +@use "../../scss/globals.scss"; + +.header { + display: flex; + justify-content: space-between; + align-items: center; + gap: calc(globals.$spacing-unit * 2); + -webkit-app-region: drag; + width: 100%; + padding: calc(globals.$spacing-unit * 2) calc(globals.$spacing-unit * 3); + color: globals.$muted-color; + border-bottom: solid 1px globals.$border-color; + + &--dragging-disabled { + -webkit-app-region: no-drag; + } + + &--is-windows { + -webkit-app-region: no-drag; + } + + &__search { + background-color: globals.$dark-background-color; + display: inline-flex; + transition: all ease 0.2s; + width: 200px; + align-items: center; + border-radius: 8px; + border: solid 1px globals.$border-color; + height: 40px; + -webkit-app-region: no-drag; + &:hover { + border-color: rgba(255, 255, 255, 0.5); + } + + &--focused { + width: 250px; + border-color: #dadbe1; + } + } + + &__search-input { + background-color: transparent; + border: none; + width: 100%; + height: 100%; + outline: none; + color: #dadbe1; + cursor: default; + font-family: inherit; + text-overflow: ellipsis; + + &:focus { + cursor: text; + } + } + + &__action-button { + color: inherit; + cursor: pointer; + transition: all ease 0.2s; + padding: globals.$spacing-unit; + + &:hover { + color: #dadbe1; + } + } + + &__section { + display: flex; + align-items: center; + gap: calc(globals.$spacing-unit * 2); + height: 100%; + overflow: hidden; + } + + &__back-button { + color: globals.$body-color; + cursor: pointer; + -webkit-app-region: no-drag; + position: absolute; + transition: transform ease 0.2s; + animation-duration: 0.2s; + width: 16px; + height: 16px; + display: flex; + align-items: center; + opacity: 0; + pointer-events: none; + animation-name: slide-out; + + &--enabled { + animation: slide-in; + opacity: 1; + pointer-events: all; + } + } + + &__title { + transition: all ease 0.2s; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + + &--has-back-button { + transform: translateX(28px); + width: calc(100% - 28px); + } + } +} + +@keyframes slide-in { + 0% { + transform: translateX(20px); + opacity: 0; + } + 100% { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes slide-out { + 0% { + transform: translateX(0px); + opacity: 1; + } + 100% { + transform: translateX(20px); + opacity: 0; + } +} diff --git a/src/renderer/src/components/header/header.tsx b/src/renderer/src/components/header/header.tsx index e0721df4..83d32caa 100644 --- a/src/renderer/src/components/header/header.tsx +++ b/src/renderer/src/components/header/header.tsx @@ -5,9 +5,11 @@ import { ArrowLeftIcon, SearchIcon, XIcon } from "@primer/octicons-react"; import { useAppDispatch, useAppSelector } from "@renderer/hooks"; -import * as styles from "./header.css"; +import "./header.scss"; + import { clearSearch } from "@renderer/features"; import { AutoUpdateSubHeader } from "./auto-update-sub-header"; +import cn from "classnames"; export interface HeaderProps { onSearch: (query: string) => void; @@ -68,16 +70,16 @@ export function Header({ onSearch, onClear, search }: HeaderProps) { return ( <>
-
+
-
-
+
+
diff --git a/src/renderer/src/components/hero/hero.css.ts b/src/renderer/src/components/hero/hero.css.ts deleted file mode 100644 index eaf0a101..00000000 --- a/src/renderer/src/components/hero/hero.css.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { style } from "@vanilla-extract/css"; - -import { SPACING_UNIT, vars } from "../../theme.css"; - -export const hero = style({ - width: "100%", - height: "280px", - minHeight: "280px", - maxHeight: "280px", - borderRadius: "4px", - color: "#DADBE1", - overflow: "hidden", - boxShadow: "0px 0px 15px 0px #000000", - cursor: "pointer", - border: `solid 1px ${vars.color.border}`, - zIndex: "1", -}); - -export const heroMedia = style({ - objectFit: "cover", - objectPosition: "center", - position: "absolute", - zIndex: "-1", - width: "100%", - height: "100%", - transition: "all ease 0.2s", - imageRendering: "revert", - selectors: { - [`${hero}:hover &`]: { - transform: "scale(1.02)", - }, - }, -}); - -export const backdrop = style({ - width: "100%", - height: "100%", - background: "linear-gradient(0deg, rgba(0, 0, 0, 0.8) 25%, transparent 100%)", - position: "relative", - display: "flex", - overflow: "hidden", -}); - -export const description = style({ - maxWidth: "700px", - color: vars.color.muted, - textAlign: "left", - lineHeight: "20px", - marginTop: `${SPACING_UNIT * 2}px`, -}); - -export const content = style({ - width: "100%", - height: "100%", - padding: `${SPACING_UNIT * 4}px ${SPACING_UNIT * 3}px`, - gap: `${SPACING_UNIT * 2}px`, - display: "flex", - flexDirection: "column", - justifyContent: "flex-end", -}); diff --git a/src/renderer/src/components/hero/hero.scss b/src/renderer/src/components/hero/hero.scss new file mode 100644 index 00000000..ea14c059 --- /dev/null +++ b/src/renderer/src/components/hero/hero.scss @@ -0,0 +1,56 @@ +@use "../../scss/globals.scss"; + +.hero { + width: 100%; + height: 280px; + min-height: 280px; + max-height: 280px; + border-radius: 4px; + color: #dadbe1; + overflow: hidden; + box-shadow: 0px 0px 15px 0px #000000; + cursor: pointer; + border: solid 1px globals.$border-color; + z-index: 1; + + &__media { + object-fit: cover; + object-position: center; + position: absolute; + z-index: -1; + width: 100%; + height: 100%; + transition: all ease 0.2s; + image-rendering: revert; + } + &:hover &__media { + transform: scale(1.02); + } + + &__backdrop { + width: 100%; + height: 100%; + background: linear-gradient(0deg, rgba(0, 0, 0, 0.8) 25%, transparent 100%); + position: relative; + display: flex; + overflow: hidden; + } + + &__description { + max-width: 700px; + color: globals.$muted-color; + text-align: left; + line-height: 20px; + margin-top: calc(globals.$spacing-unit * 2); + } + + &__content { + width: 100%; + height: 100%; + padding: calc(globals.$spacing-unit * 4) calc(globals.$spacing-unit * 3); + gap: calc(globals.$spacing-unit * 2); + display: flex; + flex-direction: column; + justify-content: flex-end; + } +} diff --git a/src/renderer/src/components/hero/hero.tsx b/src/renderer/src/components/hero/hero.tsx index 9bc5514d..b7a75c47 100644 --- a/src/renderer/src/components/hero/hero.tsx +++ b/src/renderer/src/components/hero/hero.tsx @@ -1,9 +1,9 @@ import { useNavigate } from "react-router-dom"; -import * as styles from "./hero.css"; import { useEffect, useState } from "react"; import type { TrendingGame } from "@types"; import { useTranslation } from "react-i18next"; import Skeleton from "react-loading-skeleton"; +import "./hero.scss"; export function Hero() { const [featuredGameDetails, setFeaturedGameDetails] = useState< @@ -29,7 +29,7 @@ export function Hero() { }, [i18n.language]); if (isLoading) { - return ; + return ; } if (featuredGameDetails?.length) { @@ -37,17 +37,17 @@ export function Hero() { diff --git a/src/renderer/src/components/link/link.css.ts b/src/renderer/src/components/link/link.css.ts deleted file mode 100644 index 4f0e4c41..00000000 --- a/src/renderer/src/components/link/link.css.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { style } from "@vanilla-extract/css"; - -export const link = style({ - textDecoration: "none", - color: "#C0C1C7", - ":hover": { - textDecoration: "underline", - }, -}); diff --git a/src/renderer/src/components/link/link.scss b/src/renderer/src/components/link/link.scss new file mode 100644 index 00000000..170f10f6 --- /dev/null +++ b/src/renderer/src/components/link/link.scss @@ -0,0 +1,7 @@ +.link { + text-decoration: none; + color: #c0c1c7; + &:hover { + text-decoration: underline; + } +} diff --git a/src/renderer/src/components/link/link.tsx b/src/renderer/src/components/link/link.tsx index ffd5f89c..1c3bad76 100644 --- a/src/renderer/src/components/link/link.tsx +++ b/src/renderer/src/components/link/link.tsx @@ -1,6 +1,6 @@ import { Link as ReactRouterDomLink, LinkProps } from "react-router-dom"; import cn from "classnames"; -import * as styles from "./link.css"; +import "./link.scss"; export function Link({ children, to, className, ...props }: LinkProps) { const openExternal = (event: React.MouseEvent) => { @@ -12,7 +12,7 @@ export function Link({ children, to, className, ...props }: LinkProps) { return ( @@ -22,11 +22,7 @@ export function Link({ children, to, className, ...props }: LinkProps) { } return ( - + {children} ); diff --git a/src/renderer/src/components/modal/modal.css.ts b/src/renderer/src/components/modal/modal.css.ts deleted file mode 100644 index d9d14fda..00000000 --- a/src/renderer/src/components/modal/modal.css.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { keyframes, style } from "@vanilla-extract/css"; -import { recipe } from "@vanilla-extract/recipes"; - -import { SPACING_UNIT, vars } from "../../theme.css"; - -export const scaleFadeIn = keyframes({ - "0%": { opacity: "0", scale: "0.5" }, - "100%": { - opacity: "1", - scale: "1", - }, -}); - -export const scaleFadeOut = keyframes({ - "0%": { opacity: "1", scale: "1" }, - "100%": { - opacity: "0", - scale: "0.5", - }, -}); - -export const modal = recipe({ - base: { - animation: `${scaleFadeIn} 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running`, - backgroundColor: vars.color.background, - borderRadius: "4px", - minWidth: "400px", - maxWidth: "600px", - color: vars.color.body, - maxHeight: "100%", - border: `solid 1px ${vars.color.border}`, - overflow: "hidden", - display: "flex", - flexDirection: "column", - }, - variants: { - closing: { - true: { - animationName: scaleFadeOut, - opacity: "0", - }, - }, - large: { - true: { - width: "800px", - maxWidth: "800px", - }, - }, - }, -}); - -export const modalContent = style({ - height: "100%", - overflow: "auto", - padding: `${SPACING_UNIT * 3}px ${SPACING_UNIT * 2}px`, -}); - -export const modalHeader = style({ - display: "flex", - gap: `${SPACING_UNIT}px`, - padding: `${SPACING_UNIT * 2}px`, - borderBottom: `solid 1px ${vars.color.border}`, - justifyContent: "space-between", - alignItems: "center", -}); - -export const closeModalButton = style({ - cursor: "pointer", - transition: "all ease 0.2s", - alignSelf: "flex-start", - ":hover": { - opacity: "0.75", - }, -}); - -export const closeModalButtonIcon = style({ - color: vars.color.body, -}); diff --git a/src/renderer/src/components/modal/modal.scss b/src/renderer/src/components/modal/modal.scss new file mode 100644 index 00000000..dbaee730 --- /dev/null +++ b/src/renderer/src/components/modal/modal.scss @@ -0,0 +1,77 @@ +@use "../../scss/globals.scss"; + +.modal { + animation: scale-fade-in 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none + running; + background-color: globals.$background-color; + border-radius: 4px; + min-width: 400px; + max-width: 600px; + color: globals.$body-color; + max-height: 100%; + border: solid 1px globals.$border-color; + overflow: hidden; + display: flex; + flex-direction: column; + + &--closing { + animation-name: scale-fade-out; + opacity: 0; + } + + &--large { + width: 800px; + max-width: 800px; + } + + &__content { + height: 100%; + overflow: auto; + padding: calc(globals.$spacing-unit * 3) calc(globals.$spacing-unit * 2); + } + + &__header { + display: flex; + gap: globals.$spacing-unit; + padding: calc(globals.$spacing-unit * 2); + border-bottom: solid 1px globals.$border-color; + justify-content: space-between; + align-items: center; + } + + &__close-button { + cursor: pointer; + transition: all ease 0.2s; + align-self: flex-start; + + &:hover { + opacity: 0.75; + } + } + + &__close-button-icon { + color: globals.$body-color; + } +} + +@keyframes scale-fade-in { + 0% { + opacity: 0; + scale: 0.5; + } + 100% { + opacity: 1; + scale: 1; + } +} + +@keyframes scale-fade-out { + 0% { + opacity: 1; + scale: 1; + } + 100% { + opacity: 0; + scale: 0.5; + } +} diff --git a/src/renderer/src/components/modal/modal.tsx b/src/renderer/src/components/modal/modal.tsx index eb2894de..3eceedb3 100644 --- a/src/renderer/src/components/modal/modal.tsx +++ b/src/renderer/src/components/modal/modal.tsx @@ -2,10 +2,11 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { XIcon } from "@primer/octicons-react"; -import * as styles from "./modal.css"; +import "./modal.scss"; import { Backdrop } from "../backdrop/backdrop"; import { useTranslation } from "react-i18next"; +import cn from "classnames"; export interface ModalProps { visible: boolean; @@ -102,13 +103,16 @@ export function Modal({ return createPortal(
-
+

{title}

{description &&

{description}

} @@ -117,13 +121,13 @@ export function Modal({
-
{children}
+
{children}
, document.body diff --git a/src/renderer/src/components/select-field/select-field.css.ts b/src/renderer/src/components/select-field/select-field.css.ts deleted file mode 100644 index 7acd4e98..00000000 --- a/src/renderer/src/components/select-field/select-field.css.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { style } from "@vanilla-extract/css"; -import { recipe } from "@vanilla-extract/recipes"; - -import { SPACING_UNIT, vars } from "../../theme.css"; - -export const select = recipe({ - base: { - display: "inline-flex", - transition: "all ease 0.2s", - width: "fit-content", - alignItems: "center", - borderRadius: "8px", - border: `1px solid ${vars.color.border}`, - height: "40px", - minHeight: "40px", - }, - variants: { - focused: { - true: { - borderColor: "#DADBE1", - }, - false: { - ":hover": { - borderColor: "rgba(255, 255, 255, 0.5)", - }, - }, - }, - theme: { - primary: { - backgroundColor: vars.color.darkBackground, - }, - dark: { - backgroundColor: vars.color.background, - }, - }, - }, -}); - -export const option = style({ - backgroundColor: vars.color.darkBackground, - borderRight: "4px solid", - borderColor: "transparent", - borderRadius: "8px", - width: "fit-content", - height: "100%", - outline: "none", - color: "#DADBE1", - cursor: "default", - fontFamily: "inherit", - fontSize: vars.size.body, - textOverflow: "ellipsis", - padding: `${SPACING_UNIT}px`, -}); - -export const label = style({ - marginBottom: `${SPACING_UNIT}px`, - display: "block", - color: vars.color.body, -}); diff --git a/src/renderer/src/components/select-field/select-field.scss b/src/renderer/src/components/select-field/select-field.scss new file mode 100644 index 00000000..38dfc65b --- /dev/null +++ b/src/renderer/src/components/select-field/select-field.scss @@ -0,0 +1,49 @@ +@use "../../scss/globals.scss"; + +.select-field { + display: inline-flex; + transition: all ease 0.2s; + width: fit-content; + align-items: center; + border-radius: 8px; + border: 1px solid globals.$border-color; + height: 40px; + min-height: 40px; + &:hover { + border-color: rgba(255, 255, 255, 0.5); + } + + &--focused { + border-color: #dadbe1; + } + + &--primary { + background-color: globals.$dark-background-color; + } + + &--dark { + background-color: globals.$background-color; + } + + &__option { + background-color: globals.$dark-background-color; + border-right: 4px solid; + border-color: transparent; + border-radius: 8px; + width: fit-content; + height: 100%; + outline: none; + color: #dadbe1; + cursor: default; + font-family: inherit; + font-size: globals.$body-font-size; + text-overflow: ellipsis; + padding: globals.$spacing-unit; + } + + &__label { + margin-bottom: globals.$spacing-unit; + display: block; + color: globals.$body-color; + } +} diff --git a/src/renderer/src/components/select-field/select-field.tsx b/src/renderer/src/components/select-field/select-field.tsx index fb5038f6..16b266cd 100644 --- a/src/renderer/src/components/select-field/select-field.tsx +++ b/src/renderer/src/components/select-field/select-field.tsx @@ -1,13 +1,13 @@ import { useId, useState } from "react"; -import type { RecipeVariants } from "@vanilla-extract/recipes"; -import * as styles from "./select-field.css"; +import "./select-field.scss"; +import cn from "classnames"; export interface SelectProps extends React.DetailedHTMLProps< React.SelectHTMLAttributes, HTMLSelectElement > { - theme?: NonNullable>["theme"]; + theme?: "primary" | "dark"; label?: string; options?: { key: string; value: string; label: string }[]; } @@ -25,16 +25,20 @@ export function SelectField({ return (
{label && ( -
-
- {t("my_library")} +
+ {t("my_library")} - + -
    - {filteredLibrary.map((game) => ( -
  • - -
  • - ))} -
-
+ + {getGameTitle(game)} + + + + ))} + +
+
+ {hasActiveSubscription && ( + + )} +
)} + + + handleChange({ disableNsfwAlert: !form.disableNsfwAlert }) + } + /> ); } diff --git a/src/renderer/src/vite-env.d.ts b/src/renderer/src/vite-env.d.ts index b1f45c78..304dde0f 100644 --- a/src/renderer/src/vite-env.d.ts +++ b/src/renderer/src/vite-env.d.ts @@ -1,2 +1,10 @@ /// /// + +interface ImportMetaEnv { + readonly RENDERER_VITE_INTERCOM_APP_ID: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/src/shared/index.ts b/src/shared/index.ts index 1f17ac56..173867df 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -55,6 +55,9 @@ export const removeDuplicateSpaces = (name: string) => export const replaceDotsWithSpace = (name: string) => name.replace(/\./g, " "); +export const replaceNbspWithSpace = (name: string) => + name.replace(new RegExp(String.fromCharCode(160), "g"), " "); + export const replaceUnderscoreWithSpace = (name: string) => name.replace(/_/g, " "); @@ -69,6 +72,7 @@ export const formatName = pipe( removeSpecialEditionFromName, replaceUnderscoreWithSpace, replaceDotsWithSpace, + replaceNbspWithSpace, (str) => str.replace(/DIRECTOR'S CUT/g, ""), removeSymbolsFromName, removeDuplicateSpaces, diff --git a/src/types/howlongtobeat.types.ts b/src/types/how-long-to-beat.types.ts similarity index 100% rename from src/types/howlongtobeat.types.ts rename to src/types/how-long-to-beat.types.ts diff --git a/src/types/index.ts b/src/types/index.ts index 9bb25e3f..c0269cd3 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -161,6 +161,7 @@ export interface UserPreferences { preferQuitInsteadOfHiding: boolean; runAtStartup: boolean; startMinimized: boolean; + disableNsfwAlert: boolean; } export interface Steam250Game { @@ -245,6 +246,7 @@ export interface Subscription { export interface UserDetails { id: string; username: string; + email: string | null; displayName: string; profileImageUrl: string | null; backgroundImageUrl: string | null; @@ -257,6 +259,7 @@ export interface UserProfile { id: string; displayName: string; profileImageUrl: string | null; + email: string | null; backgroundImageUrl: string | null; profileVisibility: ProfileVisibility; libraryGames: UserGame[]; @@ -373,4 +376,4 @@ export interface ComparedAchievements { export * from "./steam.types"; export * from "./real-debrid.types"; export * from "./ludusavi.types"; -export * from "./howlongtobeat.types"; +export * from "./how-long-to-beat.types"; diff --git a/yarn.lock b/yarn.lock index d241181c..0220a873 100644 --- a/yarn.lock +++ b/yarn.lock @@ -480,11 +480,6 @@ resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-2.2.2.tgz#1a6d89603fb215dc4d4178052d05b30b83c75402" integrity sha512-UNtPCbrwrenpmrXuRwn9jYpPoweNXj8X5sMvYgsqYyaH8jQ6LfUJSk3dJLnBK+6sfYPrF4iAIo5sd5HQ+tg75A== -"@canvas/image-data@^1.0.0": - version "1.0.0" - resolved "https://registry.npmjs.org/@canvas/image-data/-/image-data-1.0.0.tgz" - integrity sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw== - "@commitlint/cli@^19.5.0": version "19.5.0" resolved "https://registry.yarnpkg.com/@commitlint/cli/-/cli-19.5.0.tgz#a6e2f7f8397ddf9abd5ee5870e30a1bf51b7be2b" @@ -1071,6 +1066,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== +"@intercom/messenger-js-sdk@^0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@intercom/messenger-js-sdk/-/messenger-js-sdk-0.0.14.tgz#a27999370cc0a82a2a57a779426df25a57891863" + integrity sha512-2dH4BDAh9EI90K7hUkAdZ76W79LM45Sd1OBX7t6Vzy8twpNiQ5X+7sH9G5hlJlkSGnf+vFWlFcy9TOYAyEs1hA== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz" @@ -1090,21 +1090,6 @@ dependencies: minipass "^7.0.4" -"@jimp/bmp@^0.22.12": - version "0.22.12" - resolved "https://registry.yarnpkg.com/@jimp/bmp/-/bmp-0.22.12.tgz#0316044dc7b1a90274aef266d50349347fb864d4" - integrity sha512-aeI64HD0npropd+AR76MCcvvRaa+Qck6loCOS03CkkxGHN5/r336qTM5HPUdHKMDOGzqknuVPA8+kK1t03z12g== - dependencies: - "@jimp/utils" "^0.22.12" - bmp-js "^0.1.0" - -"@jimp/utils@^0.22.12": - version "0.22.12" - resolved "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz" - integrity sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q== - dependencies: - regenerator-runtime "^0.13.3" - "@jridgewell/gen-mapping@^0.3.5": version "0.3.5" resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz" @@ -1620,7 +1605,7 @@ "@tokenizer/token@^0.3.0": version "0.3.0" - resolved "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz" + resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== "@tootallnate/once@2": @@ -2523,11 +2508,6 @@ bluebird@^3.5.5: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -bmp-js@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/bmp-js/-/bmp-js-0.1.0.tgz#e05a63f796a6c1ff25f4771ec7adadc148c07233" - integrity sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw== - boolean@^3.0.1: version "3.2.0" resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" @@ -3139,23 +3119,6 @@ decimal.js@^10.4.3: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== -decode-bmp@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/decode-bmp/-/decode-bmp-0.2.1.tgz#cec3e0197ec3b6c60f02220f50e8757030ff2427" - integrity sha512-NiOaGe+GN0KJqi2STf24hfMkFitDUaIoUU3eKvP/wAbLe8o6FuW5n/x7MHPR0HKvBokp6MQY/j7w8lewEeVCIA== - dependencies: - "@canvas/image-data" "^1.0.0" - to-data-view "^1.1.0" - -decode-ico@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/decode-ico/-/decode-ico-0.4.1.tgz#e0f7373081532c7b8495bd51fb225d354e14de25" - integrity sha512-69NZfbKIzux1vBOd31al3XnMnH+2mqDhEgLdpygErm4d60N+UwA5Sq5WFjmEDQzumgB9fElojGwWG0vybVfFmA== - dependencies: - "@canvas/image-data" "^1.0.0" - decode-bmp "^0.2.0" - to-data-view "^1.1.0" - decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -3967,13 +3930,13 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-type@^19.0.0: - version "19.5.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-19.5.0.tgz#c13c5eca9c1c7270f6d5fbff70331b3c976f92b5" - integrity sha512-dMuq6WWnP6BpQY0zYJNpTtQWgeCImSMG0BTIzUBXvxbwc1HWP/E7AE4UWU9XSCOPGJuOHda0HpDnwM2FW+d90A== +file-type@^19.6.0: + version "19.6.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-19.6.0.tgz#b43d8870453363891884cf5e79bb3e4464f2efd3" + integrity sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ== dependencies: get-stream "^9.0.1" - strtok3 "^8.1.0" + strtok3 "^9.0.1" token-types "^6.0.0" uint8array-extras "^1.3.0" @@ -4529,18 +4492,6 @@ i18next@^23.11.2: dependencies: "@babel/runtime" "^7.23.2" -icojs@^0.19.4: - version "0.19.4" - resolved "https://registry.yarnpkg.com/icojs/-/icojs-0.19.4.tgz#fdbc9e61a0945ed1d331beb358d67f72cf7d78dc" - integrity sha512-86oNepPk2jAmbb96BPeucZI7HoSBobFlXDhhjIbwRb3wkQpvdBO5HO9KtMUNzMFT3qqQZsjLsfW+L0/9Rl9VqA== - dependencies: - "@jimp/bmp" "^0.22.12" - decode-ico "^0.4.1" - file-type "^19.0.0" - jpeg-js "^0.4.4" - pngjs "^7.0.0" - to-data-view "^2.0.0" - iconv-corefoundation@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz#31065e6ab2c9272154c8b0821151e2c88f1b002a" @@ -4955,11 +4906,6 @@ jiti@^1.19.1: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268" integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w== -jpeg-js@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" - integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg== - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -5961,10 +5907,10 @@ pe-library@^0.4.1: resolved "https://registry.yarnpkg.com/pe-library/-/pe-library-0.4.1.tgz#e269be0340dcb13aa6949d743da7d658c3e2fbea" integrity sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw== -peek-readable@^5.1.4: - version "5.2.0" - resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.2.0.tgz#7458f18126217c154938c32a185f5d05f3df3710" - integrity sha512-U94a+eXHzct7vAd19GH3UQ2dH4Satbng0MyYTMaQatL0pvYYL5CTPR25HBhKtecl+4bfu1/i3vC6k0hydO5Vcw== +peek-readable@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.3.1.tgz#9cc2c275cceda9f3d07a988f4f664c2080387dff" + integrity sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw== pend@~1.2.0: version "1.2.0" @@ -6011,11 +5957,6 @@ plist@^3.0.4, plist@^3.0.5, plist@^3.1.0: base64-js "^1.5.1" xmlbuilder "^15.1.1" -pngjs@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-7.0.0.tgz#a8b7446020ebbc6ac739db6c5415a65d17090e26" - integrity sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow== - possible-typed-array-names@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" @@ -6262,11 +6203,6 @@ reflect.getprototypeof@^1.0.4: globalthis "^1.0.3" which-builtin-type "^1.1.3" -regenerator-runtime@^0.13.3: - version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== - regenerator-runtime@^0.14.0: version "0.14.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" @@ -6971,13 +6907,13 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== -strtok3@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-8.1.0.tgz#9234a6f42ee03bf8569c7ae0788d5fd4e67e095b" - integrity sha512-ExzDvHYPj6F6QkSNe/JxSlBxTh3OrI6wrAIz53ulxo1c4hBJ1bT9C/JrAthEKHWG9riVH3Xzg7B03Oxty6S2Lw== +strtok3@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-9.0.1.tgz#7e3d7bbd2b829c9def6a7bb90d82e240abdd32be" + integrity sha512-ERPW+XkvX9W2A+ov07iy+ZFJpVdik04GhDA4eVogiG9hpC97Kem2iucyzhFxbFRvQ5o2UckFtKZdp1hkGvnrEw== dependencies: "@tokenizer/token" "^0.3.0" - peek-readable "^5.1.4" + peek-readable "^5.3.1" sudo-prompt@^9.2.1: version "9.2.1" @@ -7154,16 +7090,6 @@ tmp@^0.2.0: resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae" integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w== -to-data-view@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/to-data-view/-/to-data-view-1.1.0.tgz#08d6492b0b8deb9b29bdf1f61c23eadfa8994d00" - integrity sha512-1eAdufMg6mwgmlojAx3QeMnzB/BTVp7Tbndi3U7ftcT2zCZadjxkkmLmd97zmaxWi+sgGcgWrokmpEoy0Dn0vQ== - -to-data-view@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-data-view/-/to-data-view-2.0.0.tgz#4cc3f5c9eb59514a7436fc54c587c3c34c9b1d60" - integrity sha512-RGEM5KqlPHr+WVTPmGNAXNeFEmsBnlkxXaIfEpUYV0AST2Z5W1EGq9L/MENFrMMmL2WQr1wjkmZy/M92eKhjYA== - to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" From 47a5f4d32762f2f43ca0ebfd6251862e62d4f3a8 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Tue, 17 Dec 2024 11:10:25 -0300 Subject: [PATCH 003/291] feat: add reset achievements modal --- src/locales/en/translation.json | 5 +- src/locales/pt-BR/translation.json | 5 +- .../modals/game-options-modal.tsx | 19 ++++++++ .../modals/reset-achievements-modal.tsx | 46 +++++++++++++++++++ 4 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 src/renderer/src/pages/game-details/modals/reset-achievements-modal.tsx diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 940e3185..f2adae8b 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -168,7 +168,10 @@ "select_folder": "Select folder", "backup_from": "Backup from {{date}}", "custom_backup_location_set": "Custom backup location set", - "no_directory_selected": "No directory selected" + "no_directory_selected": "No directory selected", + "reset_achievements": "Reset achievements", + "reset_achievements_description": "This will reset all achievements for {{game}}", + "reset_achievements_title": "Are you sure?" }, "activation": { "title": "Activate Hydra", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index e724cdc3..30d8f322 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -164,7 +164,10 @@ "select_folder": "Selecione a pasta", "manage_files_description": "Gerencie quais arquivos serão feitos backup", "clear": "Limpar", - "no_directory_selected": "Nenhum diretório selecionado" + "no_directory_selected": "Nenhum diretório selecionado", + "reset_achievements": "Resetar conquistas", + "reset_achievements_description": "Isso irá resetar todas as conquistas de {{game}}", + "reset_achievements_title": "Tem certeza?" }, "activation": { "title": "Ativação", diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx index e5c83ec4..0d1fdc2c 100644 --- a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx @@ -7,6 +7,7 @@ import { gameDetailsContext } from "@renderer/context"; import { DeleteGameModal } from "@renderer/pages/downloads/delete-game-modal"; import { useDownload, useToast } from "@renderer/hooks"; import { RemoveGameFromLibraryModal } from "./remove-from-library-modal"; +import { ResetAchievementsModal } from "./reset-achievements-modal"; import { FileDirectoryIcon, FileIcon } from "@primer/octicons-react"; export interface GameOptionsModalProps { @@ -29,6 +30,8 @@ export function GameOptionsModal({ const [showDeleteModal, setShowDeleteModal] = useState(false); const [showRemoveGameModal, setShowRemoveGameModal] = useState(false); + const [showResetAchievementsModal, setShowResetAchievementsModal] = + useState(false); const { removeGameInstaller, @@ -134,6 +137,13 @@ export function GameOptionsModal({ game={game} /> + setShowResetAchievementsModal(false)} + // resetAchievements={handleResetAchievements} + game={game} + /> + {t("remove_from_library")} + + + + + +
+ + ); +} From ac6eb247df4cfe5d57172bc10fbda0ccefbc31b1 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Tue, 17 Dec 2024 13:15:55 -0300 Subject: [PATCH 004/291] feat: implement reset game achievements functionality --- src/main/events/index.ts | 1 + .../events/library/reset-game-achievements.ts | 52 +++++++++++++++++++ src/preload/index.ts | 2 + src/renderer/src/declaration.d.ts | 2 +- .../modals/game-options-modal.tsx | 7 ++- .../modals/reset-achievements-modal.tsx | 6 +-- 6 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 src/main/events/library/reset-game-achievements.ts diff --git a/src/main/events/index.ts b/src/main/events/index.ts index eff62531..e26ed91c 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -25,6 +25,7 @@ import "./library/verify-executable-path"; import "./library/remove-game"; import "./library/remove-game-from-library"; import "./library/select-game-wine-prefix"; +import "./library/reset-game-achievements"; import "./misc/open-checkout"; import "./misc/open-external"; import "./misc/show-open-dialog"; diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts new file mode 100644 index 00000000..7780c3ed --- /dev/null +++ b/src/main/events/library/reset-game-achievements.ts @@ -0,0 +1,52 @@ +import { gameAchievementRepository, gameRepository } from "@main/repository"; +import { registerEvent } from "../register-event"; +import { findAchievementFiles } from "@main/services/achievements/find-achivement-files"; +import fs from "fs"; +import { WindowManager } from "@main/services"; +import { getUnlockedAchievements } from "../user/get-unlocked-achievements"; + +const resetGameAchievements = async ( + _event: Electron.IpcMainInvokeEvent, + gameId: number +) => { + const game = await gameRepository.findOne({ where: { id: gameId } }); + + if (!game) return; + + const achievementFiles = findAchievementFiles(game); + + if (achievementFiles.length) { + try { + await Promise.all( + achievementFiles.map(async (achievementFile) => { + await fs.promises.rm(achievementFile.filePath, { recursive: true }); + }) + ); + } catch (error) { + console.error(error); + } + } + + await gameAchievementRepository.update( + { objectId: game.objectID }, + { + unlockedAchievements: null, + achievements: null, + } + ); + + // TODO: remove from db + + const gameAchievements = await getUnlockedAchievements( + game.objectID, + game.shop, + false + ); + + WindowManager.mainWindow?.webContents.send( + `on-update-achievements-${game.objectID}-${game.shop}`, + gameAchievements + ); +}; + +registerEvent("resetGameAchievements", resetGameAchievements); diff --git a/src/preload/index.ts b/src/preload/index.ts index f9d19644..d100228f 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -110,6 +110,8 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("deleteGameFolder", gameId), getGameByObjectId: (objectId: string) => ipcRenderer.invoke("getGameByObjectId", objectId), + resetGameAchievements: (gameId: number) => + ipcRenderer.invoke("resetGameAchievements", gameId), onGamesRunning: ( cb: ( gamesRunning: Pick[] diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 93c423e0..343c3ffe 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -105,7 +105,7 @@ declare global { ) => void ) => () => Electron.IpcRenderer; onLibraryBatchComplete: (cb: () => void) => () => Electron.IpcRenderer; - + resetGameAchievements: (gameId: number) => Promise; /* User preferences */ getUserPreferences: () => Promise; updateUserPreferences: ( diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx index 0d1fdc2c..c4592e13 100644 --- a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx @@ -122,6 +122,11 @@ export function GameOptionsModal({ const shouldShowWinePrefixConfiguration = window.electron.platform === "linux"; + const handleResetAchievements = async () => { + await window.electron.resetGameAchievements(game.id); + updateGame(); + }; + return ( <> setShowResetAchievementsModal(false)} - // resetAchievements={handleResetAchievements} + resetAchievements={handleResetAchievements} game={game} /> diff --git a/src/renderer/src/pages/game-details/modals/reset-achievements-modal.tsx b/src/renderer/src/pages/game-details/modals/reset-achievements-modal.tsx index 409fedbe..d8861076 100644 --- a/src/renderer/src/pages/game-details/modals/reset-achievements-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/reset-achievements-modal.tsx @@ -7,19 +7,19 @@ interface ResetAchievementsModalProps { visible: boolean; game: Game; onClose: () => void; -// resetAchievements: () => Promise; + resetAchievements: () => Promise; } export function ResetAchievementsModal({ onClose, game, visible, -// resetAchievements, + resetAchievements, }: ResetAchievementsModalProps) { const { t } = useTranslation("game_details"); const handleResetAchievements = async () => { - // await resetAchievements(); + await resetAchievements(); onClose(); }; From afcfcbf4828314fd09ec886e37881c8d4e8d8ed8 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Tue, 17 Dec 2024 13:55:45 -0300 Subject: [PATCH 005/291] refactor: clean up reset game achievements logic --- src/main/events/library/reset-game-achievements.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts index 7780c3ed..25344076 100644 --- a/src/main/events/library/reset-game-achievements.ts +++ b/src/main/events/library/reset-game-achievements.ts @@ -31,7 +31,6 @@ const resetGameAchievements = async ( { objectId: game.objectID }, { unlockedAchievements: null, - achievements: null, } ); @@ -40,7 +39,7 @@ const resetGameAchievements = async ( const gameAchievements = await getUnlockedAchievements( game.objectID, game.shop, - false + true ); WindowManager.mainWindow?.webContents.send( From 201d89e2c4cf1a070e41fba5e03580470b0238e1 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 25 Dec 2024 21:39:59 +0000 Subject: [PATCH 006/291] feat: adding torbox integration --- src/main/main.ts | 3 +++ .../services/download/download-manager.ts | 14 ++++++++++ src/main/services/download/torbox.ts | 23 ++++++++-------- src/renderer/src/assets/icons/torbox.webp | Bin 0 -> 12150 bytes src/renderer/src/constants.ts | 1 + .../src/pages/downloads/download-group.tsx | 25 +++++++++++++++++- .../modals/download-settings-modal.tsx | 8 +++--- src/shared/constants.ts | 1 + src/shared/index.ts | 2 +- 9 files changed, 58 insertions(+), 19 deletions(-) create mode 100644 src/renderer/src/assets/icons/torbox.webp diff --git a/src/main/main.ts b/src/main/main.ts index add619e1..81916174 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -11,6 +11,7 @@ import { uploadGamesBatch } from "./services/library-sync"; import { Aria2 } from "./services/aria2"; import { Downloader } from "@shared"; import { IsNull, Not } from "typeorm"; +import { TorBoxClient } from "./services/download/torbox"; const loadState = async (userPreferences: UserPreferences | null) => { import("./events"); @@ -21,6 +22,8 @@ const loadState = async (userPreferences: UserPreferences | null) => { RealDebridClient.authorize(userPreferences?.realDebridApiToken); } + TorBoxClient.authorize("7371d5ec-52fa-4b87-9052-0c8c96d947cc"); + Ludusavi.addManifestToLudusaviConfig(); HydraApi.setupApi().then(() => { diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index 80a3f6fb..1c91c4dc 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -20,6 +20,7 @@ import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity import { RealDebridClient } from "./real-debrid"; import path from "path"; import { logger } from "../logger"; +import { TorBoxClient } from "./torbox"; export class DownloadManager { private static downloadingGameId: number | null = null; @@ -29,6 +30,7 @@ export class DownloadManager { game?.status === "active" ? await this.getDownloadPayload(game).catch(() => undefined) : undefined, + initialSeeding?.map((game) => ({ game_id: game.id, url: game.uri!, @@ -294,6 +296,18 @@ export class DownloadManager { save_path: game.downloadPath!, }; } + case Downloader.TorBox: { + const downloadUrl = await TorBoxClient.getDownloadUrl(game.uri!); + console.log(downloadUrl); + + if (!downloadUrl) return; + return { + action: "start", + game_id: game.id, + url: downloadUrl, + save_path: game.downloadPath!, + }; + } } } diff --git a/src/main/services/download/torbox.ts b/src/main/services/download/torbox.ts index 3eade81d..1ef57768 100644 --- a/src/main/services/download/torbox.ts +++ b/src/main/services/download/torbox.ts @@ -20,7 +20,7 @@ export class TorBoxClient { Authorization: `Bearer ${apiToken}`, }, }); - this.apiToken = apiToken; + this.apiToken = "7371d5ec-52fa-4b87-9052-0c8c96d947cc"; } static async addMagnet(magnet: string) { @@ -55,22 +55,16 @@ export class TorBoxClient { } static async requestLink(id: number) { - const searchParams = new URLSearchParams({}); - - searchParams.set("token", this.apiToken); - searchParams.set("torrent_id", id.toString()); - searchParams.set("zip_link", "true"); + const searchParams = new URLSearchParams({ + token: this.apiToken, + torrent_id: id.toString(), + zip_link: "true", + }); const response = await this.instance.get( "/torrents/requestdl?" + searchParams.toString() ); - if (response.status !== 200) { - logger.error(response.data.error); - logger.error(response.data.detail); - return null; - } - return response.data.data; } @@ -94,4 +88,9 @@ export class TorBoxClient { const torrent = await this.addMagnet(magnetUri); return torrent.torrent_id; } + + static async getDownloadUrl(uri: string) { + const id = await this.getTorrentId(uri); + return this.requestLink(id); + } } diff --git a/src/renderer/src/assets/icons/torbox.webp b/src/renderer/src/assets/icons/torbox.webp new file mode 100644 index 0000000000000000000000000000000000000000..68d68531b4ebc3e12028bedd4d3946060d4196d0 GIT binary patch literal 12150 zcmV-+FNx4nNk&F)F8}~nMM6+kP&iCtF8}~9|G|F{RSP4xZ6i7Q|F4s__N>>4m;n7^ z>{A69WCJQm*X9xE_yF&Qun%mPlXUL1%sU@Z-bukhA!X5L#x(!`dE0WHTc6MBegyr% z`Wf8y{kQEt>$94(?&o#5_y7O znA^sXCw1}wob0OXX>6OV*tTt+S!^e(a;K>>PSuX>tYB!{PU{RQNRp&TlHR)?*h$+x zW@ctai#o=JD(ja)jwDHuWVXLp{q>kwhGS;FI0jWHK>vTf?e3Rf?4-7B+r4YrnQ8yV zjOsdMS9eBj+qR8uS#8_4`<(zf{r|tZ6Gw=+z4xBpd+)vX-g~!7z4tli{Eq|#px|Qx zvR?9eV$8&xJw9Tl8o)plJUa@y6-HPw@a#+|hyhjK251J( zbaY({2;fM-MPx(=dux+7D>c%LD*bc{3b(>2GAxH&ej$W9psz|0Y| z2hft4nHeJwpgDK*49sp$%`VS?nVFfHDtht^y5t!&bA(ZMR7cvOIqKNVj-MR0JOW&X z$l>1%m2JuF#w#5&GlX1v(M!!(>dcY#w&W2ksZ0}il9?goX3DKy128Q!hs^eyfFbM5 z5<|qyu#5o&0RRX!y?R}??y}pmZ8tC5#@1SxnQIaR07xR~|F4;z?d~oR!ESME1r_kF z{}W(-FA9q?McKU*WhM$+xEB=_brIbX1@TUnsYj?2#fh>+`(q~`N%W1Vh$s}`M6seq zqKTr%v9qFQqQj!5jiD_Wp+rq)Cb|~8_`zo;YElYnqP(K_L|0;0Z~lDp${Wr^c|;$J z{t&&0-LUpUp}b0pR*UY(I=Q!^wW3T>bdVC25%m-u zjdfP^K-6b@!jmXP#`mJ*u}+Ilh`zs!uq294lp&hk6zh1?Ddm;%R^g~c)Jim0bTih& z{$H1lK6p&U2NzA~5oSc`^AT+kJ&kE8 z+M3sU&l+At5u&Q1-$f5&incEI=T-xYsIsW9=yXikYu(l33MnUd0iyFUg)c$I02z5g zi6~L@mS|ZmrgH6SpK=d_h^T~Uh3IZf?f0CL=yg#{@DP<0brl_qsopB;ZDB)3ij1DC z9#enS2V6$D_*79#(bT4xfldFq=uZt3EuvJ zFZ%faqXQn`KAurY>GHqJyN{x?(GjO5<7XLpHLg+gYW|l=k&GA78Ec|NQ)|?=g^JpU z7K?62r#!U%oaaJSt%s;rLv+rDhwh{%14TzJ zrmA1bCMr=w(Uc#J4*R1!E^4^?%9V_~NB5r5ao_XkeonEf&no)p_XkGj9oTyt6Q%#7 zRy~|(`xQnfzCsz>A5NuOK36p4Wuh~`%;|k@i#mzYMF0HJ=+r}<-fpVWx5|jmD^s*) zZ=-9A)<5uDmGtN~WULe2iw@rWq))krI#d#s5`8B+79G7T8uZ$OD5OB5d_6^{%F)@& zZ`AXvv}g528KU{M==8NCy2aJkoMl_IXx^Pf=f9JM%V?R5IJG&vhi-^o*znMYr!zae zK+(SEkDmDazbfi+ zxE8&$w!%kumP%53#W{(#wMH*(J-9i^Nd8t0Ikk6djo#WS;}03l)T2dILDYX*(QBvm z!kgTp8dtfiy~@#Z%e`duO;lVhN{tuuq|u9?^kN!II+0gR(aPrN$<0%mnkY#hsyzlA zRrKicAM9&Y84~4_(e-1SqE|P4?B9NsCbWo}FFbnq!Z)Br3zqHSezt$~^8P<7qv_9S zK&emuw~1G4EqZ#b^`ZNeyl;m^pNMufM2~Oy-$&c>lHc9va+Ra!4;1Ydz3FkEdQfY; z`B4Eke~l07Q$6i6@@n6|7FDozVxr-qN**@WUxZ{NirR@b?rqdV(VuI-hb7;V(c-mQ zqbjzpwWx(x9h0c^pNP&xb=>`ZypUt^CQ&ib57TOj3fVL*89yW{=0~48&p(W6`GcK{(AnXu+Kj`8E<;RryiGR@K8}{hkD%O`c(HTy_Kl_gO{V)mN)+bHre$= z2~YW?%~5x6@=veV>Goj7t}v?a6^hm^+S!ijDto?BRN*)3d6mcHWU?tL>LEJ${85RY zf9bZkhkJeM_2hq@R7X|bfp7X$x7x!+FMMyKGVkre7oBVp-Ki?dB^uaTi|Sl!-HxBE zI?~DA!>=|(m2UXedsq(_Iwn!=uZwzJJJ(k$xlXc)l~G$XPIM!xcK^5A%SM+e&1k@# z)S`0N?qtA>O3t#wi#@p3sNk&!x7Zzal>D!go43;xmAvV8?%F|C|BQB3HkYEJi!SZz z8Lhrsls;AT`O}u8s+W6-eiD^)iR+!(l-@Jy`%Q{oxn5_uo+&j%m2VI&pW0=(K+C9q zyL3_QuTV5uRInxUl2N$tsy}~J{O4cwzN2Paqp0y8M)m*0t)~x<*d|eESwO4wzYL+CM zn1d3Hc<@?ah_U~%9n?az;wAHnlhNZxYcW99Hve%&HL_ucPw4*V7DMHRJn+mj;7>Qb z90O)~)BiN(j_Ji$Ty!x8&c9so7eA&H+n%1+T8_c9e62l`Q6{af_al49K-&8wuQ!#x zNwn|^V=%qK51r?X(x!ShqAw0G2G!=vS&wR}B%{y*H{|(aV151#S)iy;dNf|alg1$X zq*u_G5@Y)HKr3-bTH#ulsjaX$B#rg!rB*83^Z!f#dE8RVIRKI)i51So0m#o2Fpf`l zz=WQ^uAI!hZ&Vr-KdOa~dHKN&uN&(y>QLe-e2oyt_SFhZ;N=TYc=!y++jkNs4T>J! zqDQ~z(Fz}^42U$4OUUq1sKA9(?7l}@G$iPi3ZQ(DhXG${OxFyM>wuzZMpATXjqx?RH0(i@!hzM(ydEIF z9!dNJrBe;8r*B*=)0~4pf!yD^n7+!hVbpgI5;}_~u(Kn|+Iosg<@#G2%zFMy2b9lv z606yeNj9{dEOPpurrkjP-6;IG18QbIB)OJ&nWr^-lR~a;ZMmN8^8Z0)FM#z7Xh$n0 z?H~x`;?~-}s*|CNSLQ4UfK?pSsIst;d}-v~k2;9+w88<^y+UGj0ZjF-ov4#rTfe=& z@OBjL+W`bU1D0=F4-36)AIRj+R=B_XVmS390s#O?4}tX#9(!vjA(k6kYoB8%`0PRb zIk3b5RuzbGWoipL+_;y1KZx_N!U0r0Mv`lozzlEO4Jx^=)zh*bAh{k%_y&Z%2FdY0 z^J0nT9Y`X%tQGF3I2%rV-I22-16Fo%!`jBtSnm4dL!l=R`p*HRy$DtvkO^&ME8ASP zmvK9gf4_+XhX<$28B`F7iC)cyq_Enz@1z$W!;J^|Z zSmi;C8VeiMmqu>aTfZO7yftJ^jYna?QxfU95|FnPIe7>1?n8J2SE`-A*G{>8(E+%zhk=`Ex*D zodoRFDXnh@nIadldYZNa`M0C+U!c2=LUO!yJuT#l6LO6a-ea5}?SY9reFrKj>?&Z- z4QdCgB;|%;$TP2lU254A6Gpyw?xxok@ zI-YkAzz{H4>Lqz^@(K7aK5v zm#)ATwN<TX-~J%(UB^eCKwBlv9SC@4R&IJ(L2Z5D0P)oiLdv_5_4Mr* zOD*Ru1+jH8{p9DvDR1ZL7y)3{hnKbQ;wWgXx9F`q9?X2+mjk-*N??yqDg)cuq(JRv zUv_t_`rD2N3h(ylz+%vyNNC<~^StUC1!i~KqML2e-+!D{0VNkSFs znwC{ChI_kr35oZqvxE1bDnl6<4q#_SwWZr>6nszH)3F+Lc{k=EY~D!U|3BYB6&M50 z2{0hQu1zgH#}sf+`;&vh=S2;3?hp4JXWvn|%}bFfcRg@mNRZ@Df4&c83b3CH_wya4 z=YM9u)!xG_DfRu&b6>_3uH^xsF#*;)XdJATgsg%p+}pK@jlWKsX01E9hEhs>uz&UA zK8}hb0AN&rRXo(#F|~zU!Su8}9IFxMx7EX7;d)v_+q9>*n@E%f#s$!@fSKB|8#D@> zr|o824h){=)k)Jg^BrcFw#~+;OE5Mp|I$1AL83tEZd-)=diSZBubD3uk+gl^URHz> zVQheX_UKxPi4`2-Uan2l-P44}v}LQ-P=CE!nP>31(E)a2LLPbVViXKdyLMRQx}xjN zUyZ)wKk9$WN~B9H4;&gFkZX#MbGu2U0Qkx90RKr&88&`)_bjRZa_;Mx#4{65^ry-P}eOnWZ9JIQFEZ1%8=qhMi(K;dJQAIylhw%(#nMSkARzUb*( zd#)bGj^mb%SVIH1r&OvaEQ$c2K>}8DAd|gs2bdK3_0x8@Er*6KOWW4GUoNSV_Sm~a z1RQ{2LXvxBzE$i^3i_r2Acl$9Tbd%3o;@s~+cdarA!s%%oed=Wzi?2&POYAZO> z$nT!EhkZ37c3m~_7ObZ=RAsw5mN{U|0FCo!mbH8!Uwhi{z`$94Rd4+6rKGACyW(9N zHDolyJ&%7% zTXu2{MUc(=7}Eq6H*&yE{&Pu@|2*wm42xWqcbxglK~f}H6`S$^A0ltM-CI-nTBHzwGkL!E*w$l-?>Ap_YxzI(1Bg2<*9mal;7g^5`+Mi9M;jJeF@);JQ{g000$X$`JCw z=K3*9pkX5ksE?m_cY#J;9pOXY=0YlM{?7PRfs*~~FG7qZ%RRg}7hRPXM@W8}fR+hA zdk3z+ty9rhZF;*!iA~590*ocF-j7ymLM+d{+1Ni{gN0pfTcy(^mZ&#Q8A`srstjk` zZ~}XBd>h+Jj*Ift2>Ua>H%`IOTyLP#d6CRA=e><6wQgmBIW(e3a*gpbv$gC-mAqs9 z`ZbOue1hLR>&#vFGx?4)Ld()cjv)n>@3XF!dQP{zVTJq2&xcdrJV@yHXBJt)*0pre zap!$ZNswVsft6p(@V54QEH6Cy5b$YV4g_dcgi}V*WpyJKMaQmT#@9O=i6Mb*+U9tvh{)M$*%q%6eKY?G__WT5VFU_ zb6A`PFIdSX^&cn8C8JxJYK8l%&MxcVjuH_7h@;}VN5&B9C=Vp2u?1GKF{A3z1`S+{ zGPAdSKbZaamkub(6Pd=mugOKnU6yYQJaA}qfz_VS)Y`LyP0BoL$2Y%6P@1Pp?B3$)^dGRx{=UH6w>4kv$tquf`vZX`l%?kkT6u`s~Eo_lc>ryNWI zWsbGBkLGkJ{ndj44k*tnLUbo6&A#tr@Q4^wxG?C2>HVQum zjr(hsH|+tHy2k2h+6<6f?Fx`IbG80XD7vA>C!Rnf!e|34ew4oBYMJERRp(pbvp$D| zM<6sbM5!;K2-`CUT@)ID@dkEwcwO6ofmoeuy~WmsK#zav0HP5{ZA?6u2(gK!`oRr3 zu$m2-ocp$uNl_Zgqy$!@7K#%R0D&l1K!Nrp3Szdf&`5*EH_HDlTeN zo7>2~R1f`82eThmIDjFehi2_~eZkF0{12dR=IQ*F zjdIap^%Isr$K2S%Xj@O8??Z_mytQS2`Q>oRCphSpIwMBWwWqb3^NIkV!3S1)2p_NE zgsdK%burH|6ngQXfCK37tLEz9gUu*S$d;gCVibW@9oV=rwuN1h9)$Io-@XF0NTM;? z8WHhSJoj}#$ZcMVOsS&{LgFd7%#$?E z_-W)Bd~PIxT^};Hj>fvT^%hS)g!$q@{W+jjo+h*wd`}V`IsbD^0*Mv~k0qcYYdCw@ zweFOCxFnWgFoB(%ScZPRsPrw~!n*x^jK+RDpk1D)^;?$0MTgao*|iAd2#?00yPnZ$ zkItz5aM5^O7)==8&(00INhPbNdHp>j37?>8v(%e-cakDBwOU^VLCpie7s*z5Q)*4~ zN-~#HS{0!j3@5OB-*vIvv-&E}&*9*Xws`Gz7DC7e`#1JFk3?vIHjhMU=^}e#u3Y#h zm`f>je=JAg8B$R2MKtx$K%yQAK+rbnoK&c6gnY-jJdl`3aY*5qV{Q&VC|$ZJYg!Bm zG$M?t!jDlmFaz-<5&#+#u_QdR+cI?A?c^InlspJX5+S21O6R-`RMNKDq(T#gL4YZE zB7-!|28oVb@HJ&f9F7bT@pMw|PaCFu3r5>_OWg^_6~Ppqx$sv|blGN!bRtmUv1Gm2 z9@Rbc-koC9Pr0W|nG?hnJnPIm4V3D}>?#0B!lSV$&)`k(wk=!5sNbR2iY4g;Ta?(N zE|jKJ>rNC1|E+Af^90Q+!CdM;n~*IZ9B_rGxjaymYHzYgBt(G!&arp4@ONU82I@Vi zB3sC@1xzYdHbSz*8$E_<{J~Q><}c6xoTN(wcK4Jfv`kzP0l?3_dz6;vYCO*gP>Dyt zK_Zh>UC_DD1C>-kdseTDC1QaqcmjhoOc+B)9w1HZn5qa7Pa|c%JuY6%$j4FH@1%`lQi(O|a+@n);?ODC{@rYuC$6!&u&Y#ik zPJ>aU-TCihGSk2p!9r{3qBOZ&fzpI`W-GmE)p`|ksp4!}yNMwpfh{7Cuqu{Hgy=~T zh4}A$+k{;Ci_E1evUd@HEpB0AreFwl6+2_D2!HTYt~K^9{}d*vnzDH*aUccuh~>#91>=DrM6QgvC=%W!`JTaeXTA>{5Ug=+=_5?7T#$K?UqIO83Rs?DZU z>MsCnAyU}p+_w=S)-d4-bixB5e07mMtQ+^~6rJrr$3-i^zTS?Ki-~YkEPr_ehe^YDq-7<<~sNs=4iNF`UGLnQ4 zqE)fX^n*vCV20HF@1@M8XtIfgavk7{-x-(8ZFJn$wJfs5yq(90M8Y#V(w>;4sP3fL z6>@w*V(@5MqhSa&|4w(-7AHxdQ5~b3Z6PVL?!q6DCbWY1LR?G3gd-$B<>v(e2yy5# zf6_SR9g-r6PqkDRU|EY(}fy7Qq-|e7dj^@*Y(Ej6VvKRD-Pgg5D9}iwFQd-n4p+ z5@nKV(QR3ZJe_A^4F3If4~(ItQ8wq9CxS%ajAXVs_id8;x9v{A7-ojZYFP`a`)+w4 zAvlBQ>w+%bbB}i7Ce9$`p>(D0h3Y5l&>UyLxI`iK@4p`$aK^r-H5*&^x_*M=3=%`Y zb)#PQ+#{q5ZLGmJnzP_c?^xZSkE4*sz!_$S%vta=Nxch=i|6YAXYBf}y<16j-rf6u zm{^0CdSa$`@{N)?robBes=Ybr9-GjJ-J-o2Vf0xZ!-ry)48sX;&_86fMheN zZ|)J&B{uFz6&Ocw^^G0&S_J0EQG45=`W_mWEN%fXN2xpBO5fbpttzrdChoXwSKrvV zrt&O~Ibd?BvUP6Ou#3TIfH%apW_DW*v99GP)LzqlVGe#jjZcheg}DuLUKvSe4SS~E z@`IR|!%X8_^S^}DhLk^8bDjTuG)|jRuhnCl9fsqMY`L=))g(K@#C(}1w)Yi$t5a9c z{_-yX%)yHs5!E7l-x;0miB5q7d6Cywj~<`k4yeA!T=batpz6ncku6%yBLE0MzF>T& zRPX3na*yMVeN8OZ3^nP_c^&tYz`$Iv86@rj3!V%wQoYfikAL;DY2b4hKrc2XWpIbaT+DzN6g zjSL~?-Kp7wzQ7d3;1MU_?`{LgfICDSNpGBS^`zXxDJ4QC5uog@3$3AdCsi7An?E@INM;%HKS!yO z%a%^a7FTgD0+cqNvvng~Rl05~h&_0g(mjGotYs~yHri~B`vn}7H}ed^c2r?6ox(La z_6R=EX}D^zoz&fT7xKoOfEu}Kf0ZisjAnE31bdiOG3rv~nVj>?E5qr(3IeK{Cv5%| zQVm*tm0cBJk9FfSg{_#&4lwqvcn}bvGQWt0-$Iq6XRBO}Jwyxvy;4Xi*1Qy#wDB!) zP@QkQxf-(RWxR3x!N32teE+jDtaY!BnQ!nPj{}1I);@))U{9{rk4Nx_bFQuujNZ^k z{p4d1K;i{|P>qi7<$ygraCq$P(7;_b?|rPf&aR6HK$E9%tv#q9muAX648jYH!B&B> zBTUFv=|XEA2UNvjPNV{BoUm&l_(N1gsBbR=VedP=)8=a2Q{aHENP_U6sPT%OfIrBe zGXGdd{g@RyqfI#hlwPH2&Q(}h;*2>4iA1C6&Bpe}b@y2rNfQ_b7l1U0qmsrMSB}m9 z8q>uV1`#iLVg9fMKVodUNab0;0daB`4Np9znEkw$N$>~1{Ig>1ukSj8oE)1h?g;RQ2M#tqg4+gb-K$xnzQ7zr<8@9zgD>)0wqiTUHwPSp?E0>~2Sq8R zvZHY^l>6cf-~u%H1+(va#pJyADTQl#;1GjE0I|5ZV+0JWYO3~V@0|yM!Ikn!}?>nQ{<>|b);-KN*nc8d> z+fJ@Fz#=htR5xbNeSNWH%n4{}O1V}_zVwDTgv65Y8Ei-dX!{oaTdB~LT6JCogTR9m z0b0L>37JYM-%))p2OOe@`x=)hr8079w!*<>4?gBDSBN!E`A-sDR(nO2nv^S}P#Rz| zcv*#Lu_NZ#>=9f=9x%dY@yoY^CenNd@HG z_wn5XM$3{rM^}JNt+!V}@R^w@b5^;5$%+sqfYCA}_R_#)^Y8QrL7cYr&1FEWamEuU zSYWhZs=&T71up3p0Hg5~o_#nL5G`>>kAc;ax#pbLAs}~Yy3{eTS`yo6TJV5E4-rSh z0;|1S3!&cAEg)mX-X{&)nlt(#V> z0;!CqpwsDqKp`qsicGCk{<(LXt@cfwB2|)5XsLpmi5 zlAMbqWNC^(Aovk45EIHYHjzuenwKI&<_2|#w8vF4iI;@1w9Q?xz(FD6NR?`Da_TR8 zt#~@-Izu`l6_RWUE0U!!f`G6_6rZsoRbL+b3QfqD>p)*gY?2j;U@6@pV8nC8EaF^? zcxCk9+be&TUo6)X(m83Gv>Q9j)+^RjdWi1O#p{Wn;q*am!2}gn~BXcJ(I8aAKAl3%e zo&rnw)$Or)Z$mQ26uLm#Bt;ZSeH6>k^FXK}a<%8SS~t><8J+fCKnL4N?FIU;_#lL3 zEP@;H2+@f+R-I?|rz)I*_DRR2PSPV1w`K3V5i~>~VpntiwskGqagY8brshbyq(Bnw zD=m}16Jd(TKN!YR;H(=}tW5*RSsWNAD z1??=$lsZ8TktRv5TnTqv!RCVRjf0BYSD4FR>bAieCOm;oxMp^UR6(*ZSNu-IRYV11 zS6!0W<^kHWCuYlA0IiaKlCE5Zx#Q3X^v)rcZbn&t{uC1-Al z8z}6^W5;(My0YOuk&<6X(O@#(XXQbBO zFgINQ;x9xS;!saMsbR|5BB8N~>~{cp(jlph^jDFn=Bh&@NC;2F)}UH)b@dNy5%;Tq z)`1GZfr_h*5c4;kfzp%EAfsiq`5kFEU{CEgCn(A zH$G9=RKD-4pm6D=y4yFG&z&G(=N6*&2b2QF#&et6lk&wE5cRHw54u?-g1P@FL1of8X@wL}SSgtMZv#RZ5sX;Y2+s+qxyo$0r~lw8k(NopBxMrX z{BS=Izm`uSs?Vv->c;Gkh$_!Q`bD~F{)nI>?jhJ~rwwLBl)9mlz6DURABj!6S0uXm zzhDa~g z%=~rU2*Fj`DJ=*Vnxp1FnWy!*BL6@NBJqEv`7J^VQG$a#9pDAVuwgnRvrQ$ghg8g! z^}3k%=1!n<7iCP09iP-eBPa$QJOy_P&xI~0GBG1Q2c_i~9!KR403w%?@!PsK_>MTfO zB-bMGnJ$k0e-OC~qPvF}@u@BQ8Is6Xnm%Gj6FZeZ;7iv-s^IGCx|l|e{(qp{6)fTg zqI|Qw=n+ZRN!WEatwfLzf4uI^_|BI74#}4!ZkoA7gaX3y5Qytip5!VK{P zap*7oNIytsrl3P3$cTTRVD0>*AE~WC|8hlMcMUN!1P2ZraIiC|f3z+a$}mo8lr+9fE<#AJpyIN%J7 zf5f@FZP{zC5sA9?}AK+aiDgAKj325bDAVIQ{zLz6?}(a zB5Dx(&b7fLCJDdpdSqq?g7_1$>Qu0jeVVcNVzgeoEgG3QRCIZ_Bol?0nc&lJ&) zIC7&)e`%7bDfPUU@h0x;5Z#A#LSh%`5sChlT4iPkUWCJDxejUfFiE_o+vkBWM5OGN z%aB${DMd2edsFWvB1{m8i*c2I;A(_)oex5oierb09oA0WjEig;X^6xskU-%s*tGm4 z5v;v%j~%+VS(>UNBu*Bw?G~i%y&>6GPVI&JT@{e=x*^3y>!Eq_C2o*!>{WdmN}@rx~%Ib)b|79L{^X&xYt+OtzfG4i?FQ#Ql{P7M}s(xLH~9mL$i6wD7pZ%SL1(*0X}N&Q&JK z{H`rX;s!c*#uAabm@GI8QYy)^a2MKwC3Yy-@jQq0BaM= z3!#Q!BaXWj>6pYOslA#7@s~$D-lRdhMf)2T=<#t{Fu&y6#=%P0Z8Yg#k$5en=T1Pm zGbV_{sdm{tlM;Vm-7T;qun41Ly#3Cd1y{o)(<`L
- {DOWNLOADER_NAME[game.downloader]} + {game.downloader === Downloader.TorBox ? ( +
+ TorBox + TorBox +
+ ) : ( + {DOWNLOADER_NAME[game.downloader]} + )}
diff --git a/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx b/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx index 191d9ac1..8d650c17 100644 --- a/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx @@ -68,11 +68,9 @@ export function DownloadSettingsModal({ return true; }); - /* Gives preference to Real Debrid */ - const selectedDownloader = filteredDownloaders.includes( - Downloader.RealDebrid - ) - ? Downloader.RealDebrid + /* Gives preference to TorBox */ + const selectedDownloader = filteredDownloaders.includes(Downloader.TorBox) + ? Downloader.TorBox : filteredDownloaders[0]; setSelectedDownloader( diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 2d313abb..4ab7443d 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -4,6 +4,7 @@ export enum Downloader { Gofile, PixelDrain, Qiwi, + TorBox, } export enum DownloadSourceStatus { diff --git a/src/shared/index.ts b/src/shared/index.ts index 85868391..e0b09deb 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -92,7 +92,7 @@ export const getDownloadersForUri = (uri: string) => { return [Downloader.RealDebrid]; if (uri.startsWith("magnet:")) { - return [Downloader.Torrent, Downloader.RealDebrid]; + return [Downloader.Torrent, Downloader.TorBox, Downloader.RealDebrid]; } return []; From 91b1c349e797ffe154eab8838129293894d9b9c6 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 25 Dec 2024 21:59:48 +0000 Subject: [PATCH 007/291] feat: adding torbox integration --- src/main/services/download/torbox.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/services/download/torbox.ts b/src/main/services/download/torbox.ts index 1ef57768..0c0c0574 100644 --- a/src/main/services/download/torbox.ts +++ b/src/main/services/download/torbox.ts @@ -6,7 +6,6 @@ import type { TorBoxAddTorrentRequest, TorBoxRequestLinkRequest, } from "@types"; -import { logger } from "../logger"; export class TorBoxClient { private static instance: AxiosInstance; From c9ae543d3ed2a5581ea00f2256406167cb1f0f11 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Thu, 26 Dec 2024 00:41:57 +0000 Subject: [PATCH 008/291] feat: adding automatic backup on game close --- src/main/events/cloud-save/upload-save-game.ts | 12 ++++++++++-- src/main/services/process-watcher.ts | 5 +++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/events/cloud-save/upload-save-game.ts b/src/main/events/cloud-save/upload-save-game.ts index b3a514f5..d39ad177 100644 --- a/src/main/events/cloud-save/upload-save-game.ts +++ b/src/main/events/cloud-save/upload-save-game.ts @@ -40,8 +40,7 @@ const bundleBackup = async ( return tarLocation; }; -const uploadSaveGame = async ( - _event: Electron.IpcMainInvokeEvent, +export const createBackup = async ( objectId: string, shop: GameShop, downloadOptionTitle: string | null @@ -108,4 +107,13 @@ const uploadSaveGame = async ( }); }; +const uploadSaveGame = async ( + _event: Electron.IpcMainInvokeEvent, + objectId: string, + shop: GameShop, + downloadOptionTitle: string | null +) => { + return createBackup(objectId, shop, downloadOptionTitle); +}; + registerEvent("uploadSaveGame", uploadSaveGame); diff --git a/src/main/services/process-watcher.ts b/src/main/services/process-watcher.ts index c6cb7e10..4aa00bb4 100644 --- a/src/main/services/process-watcher.ts +++ b/src/main/services/process-watcher.ts @@ -7,6 +7,7 @@ import { Game } from "@main/entity"; import axios from "axios"; import { exec } from "child_process"; import { ProcessPayload } from "./download/types"; +import { createBackup } from "@main/events/cloud-save/upload-save-game"; const commands = { findWineDir: `lsof -c wine 2>/dev/null | grep '/drive_c/windows$' | head -n 1 | awk '{for(i=9;i<=NF;i++) printf "%s ", $i; print ""}'`, @@ -269,6 +270,10 @@ const onCloseGame = (game: Game) => { gamesPlaytime.delete(game.id); if (game.remoteId) { + // create backup + // todo: check for hydra cloud? + createBackup(game.objectID, game.shop, ""); + updateGamePlaytime( game, performance.now() - gamePlaytime.lastSyncTick, From 6ea1f9034b6c67c6da9cd6c8a0849f2e6902befc Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 25 Dec 2024 23:15:58 -0300 Subject: [PATCH 009/291] fix: possible fix for pixel drain and torbox cancel download --- python_rpc/http_downloader.py | 5 +++-- python_rpc/main.py | 16 ++++++++-------- python_rpc/torrent_downloader.py | 2 +- src/main/events/auth/sign-out.ts | 4 ---- src/main/services/download/download-manager.ts | 15 +++++++++++---- src/main/services/download/torbox.ts | 13 +++++++------ src/types/torbox.types.ts | 1 + 7 files changed, 31 insertions(+), 25 deletions(-) diff --git a/python_rpc/http_downloader.py b/python_rpc/http_downloader.py index 40e30ccd..71e4b57e 100644 --- a/python_rpc/http_downloader.py +++ b/python_rpc/http_downloader.py @@ -11,11 +11,12 @@ class HttpDownloader: ) ) - def start_download(self, url: str, save_path: str, header: str): + def start_download(self, url: str, save_path: str, header: str, out: str = None): if self.download: self.aria2.resume([self.download]) else: - downloads = self.aria2.add(url, options={"header": header, "dir": save_path}) + downloads = self.aria2.add(url, options={"header": header, "dir": save_path, "out": out}) + self.download = downloads[0] def pause_download(self): diff --git a/python_rpc/main.py b/python_rpc/main.py index 03df83de..7b2c54b9 100644 --- a/python_rpc/main.py +++ b/python_rpc/main.py @@ -28,14 +28,14 @@ if start_download_payload: torrent_downloader = TorrentDownloader(torrent_session) downloads[initial_download['game_id']] = torrent_downloader try: - torrent_downloader.start_download(initial_download['url'], initial_download['save_path'], "") + torrent_downloader.start_download(initial_download['url'], initial_download['save_path']) except Exception as e: print("Error starting torrent download", e) else: http_downloader = HttpDownloader() downloads[initial_download['game_id']] = http_downloader try: - http_downloader.start_download(initial_download['url'], initial_download['save_path'], initial_download.get('header')) + http_downloader.start_download(initial_download['url'], initial_download['save_path'], initial_download.get('header'), initial_download.get("out")) except Exception as e: print("Error starting http download", e) @@ -45,7 +45,7 @@ if start_seeding_payload: torrent_downloader = TorrentDownloader(torrent_session, lt.torrent_flags.upload_mode) downloads[seed['game_id']] = torrent_downloader try: - torrent_downloader.start_download(seed['url'], seed['save_path'], "") + torrent_downloader.start_download(seed['url'], seed['save_path']) except Exception as e: print("Error starting seeding", e) @@ -140,18 +140,18 @@ def action(): if url.startswith('magnet'): if existing_downloader and isinstance(existing_downloader, TorrentDownloader): - existing_downloader.start_download(url, data['save_path'], "") + existing_downloader.start_download(url, data['save_path']) else: torrent_downloader = TorrentDownloader(torrent_session) downloads[game_id] = torrent_downloader - torrent_downloader.start_download(url, data['save_path'], "") + torrent_downloader.start_download(url, data['save_path']) else: if existing_downloader and isinstance(existing_downloader, HttpDownloader): - existing_downloader.start_download(url, data['save_path'], data.get('header')) + existing_downloader.start_download(url, data['save_path'], data.get('header'), data.get('out')) else: http_downloader = HttpDownloader() downloads[game_id] = http_downloader - http_downloader.start_download(url, data['save_path'], data.get('header')) + http_downloader.start_download(url, data['save_path'], data.get('header'), data.get('out')) downloading_game_id = game_id @@ -167,7 +167,7 @@ def action(): elif action == 'resume_seeding': torrent_downloader = TorrentDownloader(torrent_session, lt.torrent_flags.upload_mode) downloads[game_id] = torrent_downloader - torrent_downloader.start_download(data['url'], data['save_path'], "") + torrent_downloader.start_download(data['url'], data['save_path']) elif action == 'pause_seeding': downloader = downloads.get(game_id) if downloader: diff --git a/python_rpc/torrent_downloader.py b/python_rpc/torrent_downloader.py index ca4c2fa8..8de8764e 100644 --- a/python_rpc/torrent_downloader.py +++ b/python_rpc/torrent_downloader.py @@ -102,7 +102,7 @@ class TorrentDownloader: "http://bvarf.tracker.sh:2086/announce", ] - def start_download(self, magnet: str, save_path: str, header: str): + def start_download(self, magnet: str, save_path: str): params = {'url': magnet, 'save_path': save_path, 'trackers': self.trackers, 'flags': self.flags} self.torrent_handle = self.session.add_torrent(params) self.torrent_handle.resume() diff --git a/src/main/events/auth/sign-out.ts b/src/main/events/auth/sign-out.ts index 6b720015..05fbaa86 100644 --- a/src/main/events/auth/sign-out.ts +++ b/src/main/events/auth/sign-out.ts @@ -2,7 +2,6 @@ import { registerEvent } from "../register-event"; import { DownloadManager, HydraApi, gamesPlaytime } from "@main/services"; import { dataSource } from "@main/data-source"; import { DownloadQueue, Game, UserAuth, UserSubscription } from "@main/entity"; -import { PythonRPC } from "@main/services/python-rpc"; const signOut = async (_event: Electron.IpcMainInvokeEvent) => { const databaseOperations = dataSource @@ -27,9 +26,6 @@ const signOut = async (_event: Electron.IpcMainInvokeEvent) => { /* Cancels any ongoing downloads */ DownloadManager.cancelDownload(); - /* Disconnects libtorrent */ - PythonRPC.kill(); - HydraApi.handleSignOut(); await Promise.all([ diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index 1c91c4dc..902a0c4c 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -21,6 +21,7 @@ import { RealDebridClient } from "./real-debrid"; import path from "path"; import { logger } from "../logger"; import { TorBoxClient } from "./torbox"; +import axios from "axios"; export class DownloadManager { private static downloadingGameId: number | null = null; @@ -262,11 +263,16 @@ export class DownloadManager { case Downloader.PixelDrain: { const id = game!.uri!.split("/").pop(); + const name = await axios + .get(`https://pixeldrain.com/api/file/${id}/info`) + .then((res) => res.data.name as string); + return { action: "start", game_id: game.id, url: `https://pixeldrain.com/api/file/${id}?download`, save_path: game.downloadPath!, + out: name, }; } case Downloader.Qiwi: { @@ -297,15 +303,16 @@ export class DownloadManager { }; } case Downloader.TorBox: { - const downloadUrl = await TorBoxClient.getDownloadUrl(game.uri!); - console.log(downloadUrl); + const { name, url } = await TorBoxClient.getDownloadInfo(game.uri!); + console.log(url, name); - if (!downloadUrl) return; + if (!url) return; return { action: "start", game_id: game.id, - url: downloadUrl, + url, save_path: game.downloadPath!, + out: name, }; } } diff --git a/src/main/services/download/torbox.ts b/src/main/services/download/torbox.ts index 0c0c0574..7e0c9089 100644 --- a/src/main/services/download/torbox.ts +++ b/src/main/services/download/torbox.ts @@ -74,7 +74,7 @@ export class TorBoxClient { return response.data.data; } - static async getTorrentId(magnetUri: string) { + static async getTorrentIdAndName(magnetUri: string) { const userTorrents = await this.getAllTorrentsFromUser(); const { infoHash } = await parseTorrent(magnetUri); @@ -82,14 +82,15 @@ export class TorBoxClient { (userTorrent) => userTorrent.hash === infoHash ); - if (userTorrent) return userTorrent.id; + if (userTorrent) return { id: userTorrent.id, name: userTorrent.name }; const torrent = await this.addMagnet(magnetUri); - return torrent.torrent_id; + return { id: torrent.torrent_id, name: torrent.name }; } - static async getDownloadUrl(uri: string) { - const id = await this.getTorrentId(uri); - return this.requestLink(id); + static async getDownloadInfo(uri: string) { + const { id, name } = await this.getTorrentIdAndName(uri); + const url = await this.requestLink(id); + return { url, name: `${name}.zip` }; } } diff --git a/src/types/torbox.types.ts b/src/types/torbox.types.ts index a53ccc4c..51e8bd12 100644 --- a/src/types/torbox.types.ts +++ b/src/types/torbox.types.ts @@ -66,6 +66,7 @@ export interface TorBoxAddTorrentRequest { torrent_id: number; name: string; hash: string; + size: number; }; } From bfdc2787d46dd24487bd24d31d8de1349ca3a0a6 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Thu, 2 Jan 2025 06:14:56 -0300 Subject: [PATCH 010/291] feat: remove hame achievements from remote db --- src/main/events/library/reset-game-achievements.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts index 25344076..2a1dc11d 100644 --- a/src/main/events/library/reset-game-achievements.ts +++ b/src/main/events/library/reset-game-achievements.ts @@ -4,6 +4,7 @@ import { findAchievementFiles } from "@main/services/achievements/find-achivemen import fs from "fs"; import { WindowManager } from "@main/services"; import { getUnlockedAchievements } from "../user/get-unlocked-achievements"; +import { HydraApi } from "@main/services/hydra-api"; const resetGameAchievements = async ( _event: Electron.IpcMainInvokeEvent, @@ -34,7 +35,11 @@ const resetGameAchievements = async ( } ); - // TODO: remove from db + try { + await HydraApi.delete(`/profile/games/${game.remoteId}/achievements`); + } catch (error) { + console.error(error); + } const gameAchievements = await getUnlockedAchievements( game.objectID, From 10766526c51b6910765bf720454812374e628755 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Thu, 2 Jan 2025 06:28:32 -0300 Subject: [PATCH 011/291] refactor: streamline resetGameAchievements with a single try catch --- .../events/library/reset-game-achievements.ts | 49 +++++++++---------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts index 2a1dc11d..ca858460 100644 --- a/src/main/events/library/reset-game-achievements.ts +++ b/src/main/events/library/reset-game-achievements.ts @@ -10,47 +10,44 @@ const resetGameAchievements = async ( _event: Electron.IpcMainInvokeEvent, gameId: number ) => { - const game = await gameRepository.findOne({ where: { id: gameId } }); + try { + const game = await gameRepository.findOne({ where: { id: gameId } }); - if (!game) return; + if (!game) return; - const achievementFiles = findAchievementFiles(game); + const achievementFiles = findAchievementFiles(game); - if (achievementFiles.length) { - try { + if (achievementFiles.length) { await Promise.all( achievementFiles.map(async (achievementFile) => { await fs.promises.rm(achievementFile.filePath, { recursive: true }); }) ); - } catch (error) { - console.error(error); } - } - await gameAchievementRepository.update( - { objectId: game.objectID }, - { - unlockedAchievements: null, - } - ); + await gameAchievementRepository.update( + { objectId: game.objectID }, + { + unlockedAchievements: null, + } + ); - try { await HydraApi.delete(`/profile/games/${game.remoteId}/achievements`); + + const gameAchievements = await getUnlockedAchievements( + game.objectID, + game.shop, + true + ); + + WindowManager.mainWindow?.webContents.send( + `on-update-achievements-${game.objectID}-${game.shop}`, + gameAchievements + ); + } catch (error) { console.error(error); } - - const gameAchievements = await getUnlockedAchievements( - game.objectID, - game.shop, - true - ); - - WindowManager.mainWindow?.webContents.send( - `on-update-achievements-${game.objectID}-${game.shop}`, - gameAchievements - ); }; registerEvent("resetGameAchievements", resetGameAchievements); From addc2a74d32761d4679cb93d58cda1802d11b5d8 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Thu, 2 Jan 2025 06:28:45 -0300 Subject: [PATCH 012/291] lint --- src/main/events/library/reset-game-achievements.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts index ca858460..5d16f270 100644 --- a/src/main/events/library/reset-game-achievements.ts +++ b/src/main/events/library/reset-game-achievements.ts @@ -44,7 +44,6 @@ const resetGameAchievements = async ( `on-update-achievements-${game.objectID}-${game.shop}`, gameAchievements ); - } catch (error) { console.error(error); } From 9849fbb31ce39f97d9cd5d0f29e270baaf72134c Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Thu, 2 Jan 2025 06:36:55 -0300 Subject: [PATCH 013/291] refactor: change ResetAchievementsModalProps to use Readonly type for better immutability --- .../pages/game-details/modals/reset-achievements-modal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/game-details/modals/reset-achievements-modal.tsx b/src/renderer/src/pages/game-details/modals/reset-achievements-modal.tsx index d8861076..b053060f 100644 --- a/src/renderer/src/pages/game-details/modals/reset-achievements-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/reset-achievements-modal.tsx @@ -3,12 +3,12 @@ import { Button, Modal } from "@renderer/components"; import * as styles from "./remove-from-library-modal.css"; import type { Game } from "@types"; -interface ResetAchievementsModalProps { +type ResetAchievementsModalProps = Readonly<{ visible: boolean; game: Game; onClose: () => void; resetAchievements: () => Promise; -} +}>; export function ResetAchievementsModal({ onClose, From 52c159fe511536e6139773ce18ceee4ddb8e52e2 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Thu, 2 Jan 2025 09:34:28 -0300 Subject: [PATCH 014/291] fix: replace console.error with achievementsLogger.error --- src/main/events/library/reset-game-achievements.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts index 5d16f270..5006f075 100644 --- a/src/main/events/library/reset-game-achievements.ts +++ b/src/main/events/library/reset-game-achievements.ts @@ -2,7 +2,7 @@ import { gameAchievementRepository, gameRepository } from "@main/repository"; import { registerEvent } from "../register-event"; import { findAchievementFiles } from "@main/services/achievements/find-achivement-files"; import fs from "fs"; -import { WindowManager } from "@main/services"; +import { achievementsLogger, WindowManager } from "@main/services"; import { getUnlockedAchievements } from "../user/get-unlocked-achievements"; import { HydraApi } from "@main/services/hydra-api"; @@ -45,7 +45,7 @@ const resetGameAchievements = async ( gameAchievements ); } catch (error) { - console.error(error); + achievementsLogger.error(error); } }; From e2f798c627cc932a82be8012be60bc1b8e432df3 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Thu, 2 Jan 2025 09:35:05 -0300 Subject: [PATCH 015/291] refactor: simplify resetGameAchievements by replacing Promise.all with a for loop --- src/main/events/library/reset-game-achievements.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts index 5006f075..763c0674 100644 --- a/src/main/events/library/reset-game-achievements.ts +++ b/src/main/events/library/reset-game-achievements.ts @@ -18,11 +18,9 @@ const resetGameAchievements = async ( const achievementFiles = findAchievementFiles(game); if (achievementFiles.length) { - await Promise.all( - achievementFiles.map(async (achievementFile) => { - await fs.promises.rm(achievementFile.filePath, { recursive: true }); - }) - ); + for (const achievementFile of achievementFiles) { + await fs.promises.rm(achievementFile.filePath); + } } await gameAchievementRepository.update( From 9672e649e41d0ed55ebde7237deaf4a0556f91a2 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Thu, 2 Jan 2025 09:36:09 -0300 Subject: [PATCH 016/291] feat: log deleted achievement files --- src/main/events/library/reset-game-achievements.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts index 763c0674..d7ea13cd 100644 --- a/src/main/events/library/reset-game-achievements.ts +++ b/src/main/events/library/reset-game-achievements.ts @@ -18,6 +18,11 @@ const resetGameAchievements = async ( const achievementFiles = findAchievementFiles(game); if (achievementFiles.length) { + achievementsLogger.info( + `deleting achievement files: ${achievementFiles + .map((file) => file.filePath) + .join(", ")}` + ); for (const achievementFile of achievementFiles) { await fs.promises.rm(achievementFile.filePath); } From f3d617a13a20e784ad89d4c6e21104a411e0733c Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Thu, 2 Jan 2025 09:37:23 -0300 Subject: [PATCH 017/291] feat: log response after deleting game achievements --- src/main/events/library/reset-game-achievements.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts index d7ea13cd..3fa6e0a8 100644 --- a/src/main/events/library/reset-game-achievements.ts +++ b/src/main/events/library/reset-game-achievements.ts @@ -35,7 +35,8 @@ const resetGameAchievements = async ( } ); - await HydraApi.delete(`/profile/games/${game.remoteId}/achievements`); + await HydraApi.delete(`/profile/games/${game.remoteId}/achievements`).then( + (res) => console.info(res)); const gameAchievements = await getUnlockedAchievements( game.objectID, From 257a71d62665a681314119b0bcb1d0dd896b2303 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Thu, 2 Jan 2025 09:37:58 -0300 Subject: [PATCH 018/291] fix: change console.info to console.log --- src/main/events/library/reset-game-achievements.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts index 3fa6e0a8..3677fc65 100644 --- a/src/main/events/library/reset-game-achievements.ts +++ b/src/main/events/library/reset-game-achievements.ts @@ -36,7 +36,8 @@ const resetGameAchievements = async ( ); await HydraApi.delete(`/profile/games/${game.remoteId}/achievements`).then( - (res) => console.info(res)); + (res) => console.log(res) + ); const gameAchievements = await getUnlockedAchievements( game.objectID, From 8cf549ff05e8e0b286681ef4f336c770f36a846b Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Fri, 3 Jan 2025 12:16:32 -0300 Subject: [PATCH 019/291] refactor: enhance logging in resetGameAchievement --- .../events/library/reset-game-achievements.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts index 3677fc65..cfd0dc8a 100644 --- a/src/main/events/library/reset-game-achievements.ts +++ b/src/main/events/library/reset-game-achievements.ts @@ -2,9 +2,8 @@ import { gameAchievementRepository, gameRepository } from "@main/repository"; import { registerEvent } from "../register-event"; import { findAchievementFiles } from "@main/services/achievements/find-achivement-files"; import fs from "fs"; -import { achievementsLogger, WindowManager } from "@main/services"; +import { achievementsLogger, HydraApi, WindowManager } from "@main/services"; import { getUnlockedAchievements } from "../user/get-unlocked-achievements"; -import { HydraApi } from "@main/services/hydra-api"; const resetGameAchievements = async ( _event: Electron.IpcMainInvokeEvent, @@ -18,12 +17,8 @@ const resetGameAchievements = async ( const achievementFiles = findAchievementFiles(game); if (achievementFiles.length) { - achievementsLogger.info( - `deleting achievement files: ${achievementFiles - .map((file) => file.filePath) - .join(", ")}` - ); for (const achievementFile of achievementFiles) { + achievementsLogger.log(`deleting ${achievementFile.filePath}`); await fs.promises.rm(achievementFile.filePath); } } @@ -35,9 +30,9 @@ const resetGameAchievements = async ( } ); - await HydraApi.delete(`/profile/games/${game.remoteId}/achievements`).then( - (res) => console.log(res) - ); + await HydraApi.delete(`/profile/games/${game.remoteId}/achievements`) + .catch((err) => achievementsLogger.error(err)) + .then((res) => achievementsLogger.log(res)); const gameAchievements = await getUnlockedAchievements( game.objectID, From 93b86f8c6c1df579eb9586cb254361134f2269a0 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Fri, 3 Jan 2025 12:38:06 -0300 Subject: [PATCH 020/291] refactor: improve reset achievements handling and modal state management --- .../game-details/modals/game-options-modal.tsx | 15 ++++++++++----- .../modals/reset-achievements-modal.tsx | 10 ++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx index 5289870e..346f4700 100644 --- a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx @@ -31,10 +31,10 @@ export function GameOptionsModal({ const [showDeleteModal, setShowDeleteModal] = useState(false); const [showRemoveGameModal, setShowRemoveGameModal] = useState(false); + const [launchOptions, setLaunchOptions] = useState(game.launchOptions ?? ""); const [showResetAchievementsModal, setShowResetAchievementsModal] = useState(false); - - const [launchOptions, setLaunchOptions] = useState(game.launchOptions ?? ""); + const [isDeleting, setIsDeleting] = useState(false); const { removeGameInstaller, @@ -146,8 +146,13 @@ export function GameOptionsModal({ window.electron.platform === "linux"; const handleResetAchievements = async () => { - await window.electron.resetGameAchievements(game.id); - updateGame(); + setIsDeleting(true); + try { + await window.electron.resetGameAchievements(game.id); + } finally { + await updateGame(); + setIsDeleting(false); + } }; const shouldShowLaunchOptionsConfiguration = false; @@ -333,7 +338,7 @@ export function GameOptionsModal({ diff --git a/src/renderer/src/pages/game-details/modals/reset-achievements-modal.tsx b/src/renderer/src/pages/game-details/modals/reset-achievements-modal.tsx index b053060f..642d32ba 100644 --- a/src/renderer/src/pages/game-details/modals/reset-achievements-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/reset-achievements-modal.tsx @@ -2,7 +2,6 @@ import { useTranslation } from "react-i18next"; import { Button, Modal } from "@renderer/components"; import * as styles from "./remove-from-library-modal.css"; import type { Game } from "@types"; - type ResetAchievementsModalProps = Readonly<{ visible: boolean; game: Game; @@ -19,18 +18,21 @@ export function ResetAchievementsModal({ const { t } = useTranslation("game_details"); const handleResetAchievements = async () => { - await resetAchievements(); - onClose(); + try { + await resetAchievements(); + } finally { + onClose(); + } }; return (
From b68fe300ba8325d2c711983f61c5cc052dbdd08f Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Fri, 3 Jan 2025 17:24:37 -0300 Subject: [PATCH 023/291] refactor: rename state variable for clarity --- .../src/pages/game-details/modals/game-options-modal.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx index b2d47dcb..e751a67b 100644 --- a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx @@ -34,7 +34,7 @@ export function GameOptionsModal({ const [launchOptions, setLaunchOptions] = useState(game.launchOptions ?? ""); const [showResetAchievementsModal, setShowResetAchievementsModal] = useState(false); - const [isDeleting, setIsDeleting] = useState(false); + const [isDeletingAchievements, setIsDeletingAchievements] = useState(false); const { removeGameInstaller, @@ -146,12 +146,12 @@ export function GameOptionsModal({ window.electron.platform === "linux"; const handleResetAchievements = async () => { - setIsDeleting(true); + setIsDeletingAchievements(true); try { await window.electron.resetGameAchievements(game.id); } finally { await updateGame(); - setIsDeleting(false); + setIsDeletingAchievements(false); } }; @@ -338,7 +338,7 @@ export function GameOptionsModal({ From ef3bf9890353996baabdf75a99263b3bd829b599 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Fri, 3 Jan 2025 17:36:55 -0300 Subject: [PATCH 024/291] feat: add success and error toast --- src/locales/en/translation.json | 4 +++- src/locales/pt-BR/translation.json | 4 +++- src/main/events/library/reset-game-achievements.ts | 3 ++- .../src/pages/game-details/modals/game-options-modal.tsx | 5 ++++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 3aea8fac..4e3dcb37 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -182,7 +182,9 @@ "no_write_permission": "Cannot download into this directory. Click here to learn more.", "reset_achievements": "Reset achievements", "reset_achievements_description": "This will reset all achievements for {{game}}", - "reset_achievements_title": "Are you sure?" + "reset_achievements_title": "Are you sure?", + "reset_achievements_success": "Achievements successfully reset", + "reset_achievements_error": "Failed to reset achievements" }, "activation": { "title": "Activate Hydra", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index b6f2360b..2a80084f 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -170,7 +170,9 @@ "no_directory_selected": "Nenhum diretório selecionado", "reset_achievements": "Resetar conquistas", "reset_achievements_description": "Isso irá resetar todas as conquistas de {{game}}", - "reset_achievements_title": "Tem certeza?" + "reset_achievements_title": "Tem certeza?", + "reset_achievements_success": "Conquistas resetadas com sucesso", + "reset_achievements_error": "Falha ao resetar conquistas" }, "activation": { "title": "Ativação", diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts index cfd0dc8a..eb55a412 100644 --- a/src/main/events/library/reset-game-achievements.ts +++ b/src/main/events/library/reset-game-achievements.ts @@ -30,7 +30,7 @@ const resetGameAchievements = async ( } ); - await HydraApi.delete(`/profile/games/${game.remoteId}/achievements`) + await HydraApi.delete(`/profile/games/achievements/${game.remoteId}`) .catch((err) => achievementsLogger.error(err)) .then((res) => achievementsLogger.log(res)); @@ -46,6 +46,7 @@ const resetGameAchievements = async ( ); } catch (error) { achievementsLogger.error(error); + throw error; } }; diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx index e751a67b..fad02a96 100644 --- a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx @@ -149,8 +149,11 @@ export function GameOptionsModal({ setIsDeletingAchievements(true); try { await window.electron.resetGameAchievements(game.id); - } finally { await updateGame(); + showSuccessToast(t("reset_achievements_success")); + } catch (error) { + showErrorToast(t("reset_achievements_error")); + } finally { setIsDeletingAchievements(false); } }; From 2ddda4e4d225860a5d5b77850db3c096cc61217d Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Fri, 3 Jan 2025 17:56:13 -0300 Subject: [PATCH 025/291] refactor: remove error logging --- src/main/events/library/reset-game-achievements.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts index eb55a412..17e00d5d 100644 --- a/src/main/events/library/reset-game-achievements.ts +++ b/src/main/events/library/reset-game-achievements.ts @@ -31,7 +31,6 @@ const resetGameAchievements = async ( ); await HydraApi.delete(`/profile/games/achievements/${game.remoteId}`) - .catch((err) => achievementsLogger.error(err)) .then((res) => achievementsLogger.log(res)); const gameAchievements = await getUnlockedAchievements( From e6d76a5dbeaaa56c21a196cb50b31d12e16e4045 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Fri, 3 Jan 2025 17:56:39 -0300 Subject: [PATCH 026/291] lint --- src/main/events/library/reset-game-achievements.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts index 17e00d5d..4d591a5d 100644 --- a/src/main/events/library/reset-game-achievements.ts +++ b/src/main/events/library/reset-game-achievements.ts @@ -30,8 +30,9 @@ const resetGameAchievements = async ( } ); - await HydraApi.delete(`/profile/games/achievements/${game.remoteId}`) - .then((res) => achievementsLogger.log(res)); + await HydraApi.delete(`/profile/games/achievements/${game.remoteId}`).then( + (res) => achievementsLogger.log(res) + ); const gameAchievements = await getUnlockedAchievements( game.objectID, From 190ddeb46ea30087f78d2c76dc1b24e727054ab5 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Fri, 3 Jan 2025 18:22:13 -0300 Subject: [PATCH 027/291] refactor: improve logging for deleted game achievements --- src/main/events/library/reset-game-achievements.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts index 4d591a5d..25b9e6d7 100644 --- a/src/main/events/library/reset-game-achievements.ts +++ b/src/main/events/library/reset-game-achievements.ts @@ -31,7 +31,7 @@ const resetGameAchievements = async ( ); await HydraApi.delete(`/profile/games/achievements/${game.remoteId}`).then( - (res) => achievementsLogger.log(res) + () => achievementsLogger.log(`Deleted achievements from ${game.remoteId} - ${game.objectID} - ${game.title}`) ); const gameAchievements = await getUnlockedAchievements( From 50616955003f6b75dd6780fa0303ac9960d33b19 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Fri, 3 Jan 2025 18:22:25 -0300 Subject: [PATCH 028/291] lint --- src/main/events/library/reset-game-achievements.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts index 25b9e6d7..8d52a3a6 100644 --- a/src/main/events/library/reset-game-achievements.ts +++ b/src/main/events/library/reset-game-achievements.ts @@ -31,7 +31,10 @@ const resetGameAchievements = async ( ); await HydraApi.delete(`/profile/games/achievements/${game.remoteId}`).then( - () => achievementsLogger.log(`Deleted achievements from ${game.remoteId} - ${game.objectID} - ${game.title}`) + () => + achievementsLogger.log( + `Deleted achievements from ${game.remoteId} - ${game.objectID} - ${game.title}` + ) ); const gameAchievements = await getUnlockedAchievements( From 2df57b071dab55d85bb6623c5cd740b5ac17fc20 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Fri, 3 Jan 2025 18:50:02 -0300 Subject: [PATCH 029/291] feat: disable reset achievement button if has no achievements --- .../src/pages/game-details/modals/game-options-modal.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx index fad02a96..3afc3870 100644 --- a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx @@ -26,7 +26,7 @@ export function GameOptionsModal({ const { showSuccessToast, showErrorToast } = useToast(); - const { updateGame, setShowRepacksModal, repacks, selectGameExecutable } = + const { updateGame, setShowRepacksModal, repacks, selectGameExecutable, achievements } = useContext(gameDetailsContext); const [showDeleteModal, setShowDeleteModal] = useState(false); @@ -43,6 +43,10 @@ export function GameOptionsModal({ cancelDownload, } = useDownload(); + const hasAchievements = + (achievements?.filter((achievement) => achievement.unlocked).length ?? 0) > + 0; + const deleting = isGameDeleting(game.id); const { lastPacket } = useDownload(); @@ -341,7 +345,7 @@ export function GameOptionsModal({ From 3efb1425b9835f191f8221e5902c6fc7cb522b2f Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Fri, 3 Jan 2025 18:50:13 -0300 Subject: [PATCH 030/291] lint --- .../src/pages/game-details/modals/game-options-modal.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx index 3afc3870..a677e4b9 100644 --- a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx @@ -26,8 +26,13 @@ export function GameOptionsModal({ const { showSuccessToast, showErrorToast } = useToast(); - const { updateGame, setShowRepacksModal, repacks, selectGameExecutable, achievements } = - useContext(gameDetailsContext); + const { + updateGame, + setShowRepacksModal, + repacks, + selectGameExecutable, + achievements, + } = useContext(gameDetailsContext); const [showDeleteModal, setShowDeleteModal] = useState(false); const [showRemoveGameModal, setShowRemoveGameModal] = useState(false); From cade56bb128c8da09301d2d2ddd1996624c96de0 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Fri, 3 Jan 2025 19:15:31 -0300 Subject: [PATCH 031/291] feat: disable reset achievements button if user is not logged in --- .../pages/game-details/modals/game-options-modal.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx index a677e4b9..b06de28a 100644 --- a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx @@ -5,7 +5,7 @@ import type { Game } from "@types"; import * as styles from "./game-options-modal.css"; import { gameDetailsContext } from "@renderer/context"; import { DeleteGameModal } from "@renderer/pages/downloads/delete-game-modal"; -import { useDownload, useToast } from "@renderer/hooks"; +import { useDownload, useToast, useUserDetails } from "@renderer/hooks"; import { RemoveGameFromLibraryModal } from "./remove-from-library-modal"; import { ResetAchievementsModal } from "./reset-achievements-modal"; import { FileDirectoryIcon, FileIcon } from "@primer/octicons-react"; @@ -48,6 +48,8 @@ export function GameOptionsModal({ cancelDownload, } = useDownload(); + const { userDetails } = useUserDetails(); + const hasAchievements = (achievements?.filter((achievement) => achievement.unlocked).length ?? 0) > 0; @@ -350,7 +352,12 @@ export function GameOptionsModal({ From 87acdea5abb4a495d668417807e4b5d34ac05d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=97=A6=F0=9D=97=B5=F0=9D=97=9F=F0=9D=97=B2?= =?UTF-8?q?=F0=9D=97=BF=F0=9D=97=A3?= <75412448+mikropsoft@users.noreply.github.com> Date: Sun, 5 Jan 2025 21:08:52 +0300 Subject: [PATCH 032/291] Update TR Locales --- src/locales/tr/translation.json | 425 +++++++++++++++++++++++++++----- 1 file changed, 361 insertions(+), 64 deletions(-) diff --git a/src/locales/tr/translation.json b/src/locales/tr/translation.json index 6757fdf5..8d0e7811 100644 --- a/src/locales/tr/translation.json +++ b/src/locales/tr/translation.json @@ -1,131 +1,428 @@ { "language_name": "Türkçe", + "app": { + "successfully_signed_in": "Başarıyla giriş yapıldı" + }, "home": { - "featured": "Öne çıkan", - "surprise_me": "Şaşırt beni", - "no_results": "Sonuç bulunamadı" + "featured": "Öne Çıkanlar", + "surprise_me": "Beni Şaşırt", + "no_results": "Sonuç bulunamadı", + "start_typing": "Aramak için yazmaya başlayın...", + "hot": "Şu anda popüler", + "weekly": "📅 Haftanın en iyi oyunları", + "achievements": "🏆 Tamamlanacak oyunlar" }, "sidebar": { "catalogue": "Katalog", - "downloads": "İndirmeler", + "downloads": "İndirilenler", "settings": "Ayarlar", - "my_library": "Kütüphane", - "downloading_metadata": "{{title}} (Metadata indiriliyor…)", - "paused": "{{title}} (Duraklatıldı)", + "my_library": "Kütüphanem", + "downloading_metadata": "{{title}} (Meta verileri indiriliyor…)", + "paused": "{{title}} (Durduruldu)", "downloading": "{{title}} ({{percentage}} - İndiriliyor…)", "filter": "Kütüphaneyi filtrele", - "home": "Ana menü" + "home": "Ana Sayfa", + "queued": "{{title}} (Sırada)", + "game_has_no_executable": "Oyun için bir çalıştırılabilir dosya seçilmedi", + "sign_in": "Giriş yap", + "friends": "Arkadaşlar", + "need_help": "Yardıma mı ihtiyacınız var?" }, "header": { - "search": "Ara", - "home": "Ana menü", + "search": "Oyunları ara", + "home": "Ana Sayfa", "catalogue": "Katalog", - "downloads": "İndirmeler", + "downloads": "İndirilenler", "search_results": "Arama sonuçları", - "settings": "Ayarlar" + "settings": "Ayarlar", + "version_available_install": "Sürüm {{version}} mevcut. Yüklemek ve yeniden başlatmak için buraya tıklayın.", + "version_available_download": "Sürüm {{version}} mevcut. İndirmek için buraya tıklayın." }, "bottom_panel": { - "no_downloads_in_progress": "İndirilen bir şey yok", - "downloading_metadata": "{{title}} metadatası indiriliyor…", - "downloading": "{{title}} indiriliyor… ({{percentage}} tamamlandı) - Bitiş {{eta}} - {{speed}}" + "no_downloads_in_progress": "Devam eden indirme yok", + "downloading_metadata": "{{title}} meta verileri indiriliyor…", + "downloading": "{{title}} indiriliyor… ({{percentage}} tamamlandı) - Tamamlama: {{eta}} - Hız: {{speed}}", + "calculating_eta": "{{title}} indiriliyor… ({{percentage}} tamamlandı) - Kalan süre hesaplanıyor…", + "checking_files": "{{title}} dosyaları kontrol ediliyor… ({{percentage}} tamamlandı)" }, "catalogue": { - "next_page": "Sonraki sayfa", - "previous_page": "Önceki sayfa" + "search": "Filtrele…", + "developers": "Geliştiriciler", + "genres": "Türler", + "tags": "Etiketler", + "publishers": "Yayıncılar", + "download_sources": "İndirme kaynakları", + "result_count": "{{resultCount}} sonuç", + "filter_count": "{{filterCount}} mevcut", + "clear_filters": "{{filterCount}} seçili filtreyi temizle" }, "game_details": { "open_download_options": "İndirme seçeneklerini aç", "download_options_zero": "İndirme seçeneği yok", "download_options_one": "{{count}} indirme seçeneği", "download_options_other": "{{count}} indirme seçeneği", - "updated_at": "{{updated_at}} güncellendi", - "install": "İndir", + "updated_at": "{{updated_at}} tarihinde güncellendi", + "install": "Yükle", "resume": "Devam et", - "pause": "Duraklat", + "pause": "Durdur", "cancel": "İptal et", - "remove": "Sil", - "space_left_on_disk": "Diskte {{space}} yer kaldı", - "eta": "Bitiş {{eta}}", - "downloading_metadata": "Metadata indiriliyor…", - "filter": "Repackleri filtrele", + "remove": "Kaldır", + "space_left_on_disk": "Diskte {{space}} boş alan kaldı", + "eta": "{{eta}} tahmini bitiş", + "calculating_eta": "Kalan süre hesaplanıyor…", + "downloading_metadata": "Meta veriler indiriliyor…", + "filter": "Paketleri filtrele", "requirements": "Sistem gereksinimleri", "minimum": "Minimum", "recommended": "Önerilen", - "release_date": "{{date}} tarihinde çıktı", - "publisher": "{{publisher}} tarihinde yayınlandı", - "hours": "saatler", - "minutes": "dakikalar", + "paused": "Durduruldu", + "release_date": "{{date}} tarihinde yayımlandı", + "publisher": "{{publisher}} tarafından yayımlandı", + "hours": "saat", + "minutes": "dakika", "amount_hours": "{{amount}} saat", "amount_minutes": "{{amount}} dakika", - "accuracy": "%{{accuracy}} doğruluk", + "accuracy": "{{accuracy}}% doğruluk", "add_to_library": "Kütüphaneye ekle", "remove_from_library": "Kütüphaneden kaldır", - "no_downloads": "İndirme yok", - "play_time": "{{amount}} oynandı", - "last_time_played": "Son oynanan {{period}}", - "not_played_yet": "Bu {{title}} hiç oynanmadı", - "next_suggestion": "Sıradaki öneri", + "no_downloads": "İndirilebilir içerik yok", + "play_time": "{{amount}} süre oynandı", + "last_time_played": "Son oynama {{period}} önce", + "not_played_yet": "{{title}} henüz oynanmadı", + "next_suggestion": "Sonraki öneri", "play": "Oyna", - "deleting": "Installer siliniyor…", + "deleting": "Yükleyici siliniyor…", "close": "Kapat", - "playing_now": "Şimdi oynanıyor", + "playing_now": "Şu anda oynanıyor", "change": "Değiştir", - "repacks_modal_description": "İndirmek istediğiiniz repacki seçin", - "select_folder_hint": "Varsayılan klasörü değiştirmek için ulaşmanız gereken ayar", - "download_now": "Şimdi" + "repacks_modal_description": "İndirmek istediğiniz paketi seçin", + "select_folder_hint": "Varsayılan klasörü değiştirmek için <0>Ayarlar bölümüne gidin", + "download_now": "Şimdi indir", + "no_shop_details": "Mağaza bilgileri alınamadı.", + "download_options": "İndirme seçenekleri", + "download_path": "İndirme yolu", + "previous_screenshot": "Önceki ekran görüntüsü", + "next_screenshot": "Sonraki ekran görüntüsü", + "screenshot": "{{number}} ekran görüntüsü", + "open_screenshot": "{{number}} ekran görüntüsünü aç", + "download_settings": "İndirme ayarları", + "downloader": "İndirici", + "select_executable": "Seç", + "no_executable_selected": "Hiçbir çalıştırılabilir dosya seçilmedi", + "open_folder": "Klasörü aç", + "open_download_location": "İndirilen dosyaları gör", + "create_shortcut": "Masaüstü kısayolu oluştur", + "clear": "Temizle", + "remove_files": "Dosyaları kaldır", + "remove_from_library_title": "Emin misiniz?", + "remove_from_library_description": "Bu işlem {{game}} oyununu kütüphanenizden kaldıracaktır", + "options": "Seçenekler", + "executable_section_title": "Çalıştırılabilir dosya", + "executable_section_description": "\"Oyna\" tıklandığında çalıştırılacak dosyanın yolu", + "downloads_secion_title": "İndirmeler", + "downloads_section_description": "Bu oyun için güncellemeleri veya diğer sürümleri kontrol edin", + "danger_zone_section_title": "Tehlike bölgesi", + "danger_zone_section_description": "Bu oyunu kütüphanenizden veya Hydra tarafından indirilen dosyaları kaldırın", + "download_in_progress": "İndirme devam ediyor", + "download_paused": "İndirme durduruldu", + "last_downloaded_option": "Son indirilen seçenek", + "create_shortcut_success": "Kısayol başarıyla oluşturuldu", + "create_shortcut_error": "Kısayol oluşturulurken hata oluştu", + "nsfw_content_title": "Bu oyun uygunsuz içerik içeriyor", + "nsfw_content_description": "{{title}} her yaş için uygun olmayabilecek içeriklere sahiptir. Devam etmek istediğinizden emin misiniz?", + "allow_nsfw_content": "Devam et", + "refuse_nsfw_content": "Geri dön", + "stats": "İstatistikler", + "download_count": "İndirme sayısı", + "player_count": "Aktif oyuncular", + "download_error": "Bu indirme seçeneği mevcut değil", + "download": "İndir", + "executable_path_in_use": "\"{{game}}\" tarafından kullanılan çalıştırılabilir dosya", + "warning": "Uyarı:", + "hydra_needs_to_remain_open": "Bu indirmenin tamamlanması için Hydra açık kalmalıdır. Eğer Hydra kapanırsa, ilerleme kaydedilmez.", + "achievements": "Başarılar", + "achievements_count": "Başarılar {{unlockedCount}}/{{achievementsCount}}", + "cloud_save": "Bulut kaydı", + "cloud_save_description": "İlerlemenizi buluta kaydedin ve herhangi bir cihazda oynamaya devam edin", + "backups": "Yedekler", + "install_backup": "Yükle", + "delete_backup": "Sil", + "create_backup": "Yeni yedek oluştur", + "last_backup_date": "{{date}} tarihindeki son yedek", + "no_backup_preview": "Bu oyun için kayıtlı oyun bulunamadı", + "restoring_backup": "Yedek geri yükleniyor ({{progress}} tamamlandı)…", + "uploading_backup": "Yedek yükleniyor…", + "no_backups": "Bu oyun için henüz bir yedek oluşturmadınız", + "backup_uploaded": "Yedek yüklendi", + "backup_deleted": "Yedek silindi", + "backup_restored": "Yedek geri yüklendi", + "see_all_achievements": "Tüm başarıları gör", + "sign_in_to_see_achievements": "Başarıları görmek için giriş yapın", + "mapping_method_automatic": "Otomatik", + "mapping_method_manual": "Manuel", + "mapping_method_label": "Eşleme yöntemi", + "files_automatically_mapped": "Dosyalar otomatik olarak eşlendi", + "no_backups_created": "Bu oyun için yedek oluşturulmadı", + "manage_files": "Dosyaları yönet", + "loading_save_preview": "Kayıtlı oyunlar aranıyor…", + "wine_prefix": "Wine Prefix", + "wine_prefix_description": "Bu oyunu çalıştırmak için kullanılan Wine Prefix", + "launch_options": "Başlatma Seçenekleri", + "launch_options_description": "İleri düzey kullanıcılar, başlatma seçeneklerine değişiklikler girebilir (deneysel özellik)", + "launch_options_placeholder": "Belirtilen bir parametre yok", + "no_download_option_info": "Bilgi mevcut değil", + "backup_deletion_failed": "Yedek silinemedi", + "max_number_of_artifacts_reached": "Bu oyun için maksimum yedek sayısına ulaşıldı", + "achievements_not_sync": "Başarılarınızı senkronize etmeyi öğrenin", + "manage_files_description": "Hangi dosyaların yedeklenip geri yükleneceğini yönetin", + "select_folder": "Klasör seç", + "backup_from": "{{date}} tarihinden yedek", + "custom_backup_location_set": "Özel yedekleme konumu ayarlandı", + "no_directory_selected": "Bir dizin seçilmedi", + "no_write_permission": "Bu dizine indirme yapılamaz. Daha fazla bilgi için buraya tıklayın.", + "reset_achievements": "Başarıları sıfırla", + "reset_achievements_description": "Bu işlem {{game}} için tüm başarıları sıfırlar", + "reset_achievements_title": "Emin misiniz?", + "reset_achievements_success": "Başarılar başarıyla sıfırlandı", + "reset_achievements_error": "Başarılar sıfırlanamadı" }, "activation": { - "title": "Hydra'yı aktif et", - "installation_id": "Kurulum ID'si:", - "enter_activation_code": "Aktifleştirme kodunuzu girin", - "message": "Bunu nerede soracağınızı bilmiyorsanız, buna sahip olmamanız gerekiyor.", - "activate": "Aktif et", + "title": "Hydra'yı Aktive Et", + "installation_id": "Kurulum Kimliği:", + "enter_activation_code": "Aktivasyon kodunuzu girin", + "message": "Bunu nereden soracağınızı bilmiyorsanız, bu sizin için olmamalı.", + "activate": "Aktive Et", "loading": "Yükleniyor…" }, "downloads": { - "resume": "Devam et", + "resume": "Devam Et", "pause": "Duraklat", - "eta": "Bitiş {{eta}}", + "eta": "Tamamlama {{eta}}", "paused": "Duraklatıldı", "verifying": "Doğrulanıyor…", "completed": "Tamamlandı", - "cancel": "İptal et", - "filter": "Yüklü oyunları filtrele", + "removed": "İndirilmedi", + "cancel": "İptal Et", + "filter": "İndirilen oyunları filtrele", "remove": "Kaldır", "downloading_metadata": "Metadata indiriliyor…", - "deleting": "Installer siliniyor…", - "delete": "Installer'ı sil", + "deleting": "Yükleyici siliniyor…", + "delete": "Yükleyiciyi kaldır", "delete_modal_title": "Emin misiniz?", - "delete_modal_description": "Bu bilgisayarınızdan tüm kurulum dosyalarını silecek", - "install": "Kur" + "delete_modal_description": "Bu işlem, tüm kurulum dosyalarını bilgisayarınızdan kaldıracaktır", + "install": "Kur", + "download_in_progress": "Devam ediyor", + "queued_downloads": "Sıradaki indirmeler", + "downloads_completed": "Tamamlananlar", + "queued": "Sırada", + "no_downloads_title": "Bomboş", + "no_downloads_description": "Henüz Hydra ile hiçbir şey indirmediniz, ancak başlamak için asla geç değil.", + "checking_files": "Dosyalar kontrol ediliyor…", + "seeding": "Paylaşılıyor", + "stop_seeding": "Paylaşımı durdur", + "resume_seeding": "Paylaşımı sürdür", + "options": "Yönet" }, "settings": { "downloads_path": "İndirme yolu", "change": "Güncelle", "notifications": "Bildirimler", - "enable_download_notifications": "Bir indirme bittiğinde", - "enable_repack_list_notifications": "Yeni bir repack eklendiğinde" + "enable_download_notifications": "Bir indirme tamamlandığında", + "enable_repack_list_notifications": "Yeni bir repack eklendiğinde", + "real_debrid_api_token_label": "Real-Debrid API anahtarı", + "quit_app_instead_hiding": "Hydra'yı kapatırken gizlemeyin", + "launch_with_system": "Hydra'yı sistem başlatıldığında çalıştır", + "general": "Genel", + "behavior": "Davranış", + "download_sources": "İndirme kaynakları", + "language": "Dil", + "real_debrid_api_token": "API Anahtarı", + "enable_real_debrid": "Real-Debrid'i Etkinleştir", + "real_debrid_description": "Real-Debrid, yalnızca internet hızınızla sınırlı olarak hızlı dosya indirmenizi sağlayan sınırsız bir indirici.", + "real_debrid_invalid_token": "Geçersiz API anahtarı", + "real_debrid_api_token_hint": "API anahtarınızı <0>buradan alabilirsiniz", + "real_debrid_free_account_error": "\"{{username}}\" hesabı ücretsiz bir hesaptır. Lütfen Real-Debrid abonesi olun", + "real_debrid_linked_message": "\"{{username}}\" hesabı bağlandı", + "save_changes": "Değişiklikleri Kaydet", + "changes_saved": "Değişiklikler başarıyla kaydedildi", + "download_sources_description": "Hydra, indirme bağlantılarını bu kaynaklardan alacak. Kaynak URL, indirme bağlantılarını içeren bir .json dosyasına doğrudan bir bağlantı olmalıdır.", + "validate_download_source": "Doğrula", + "remove_download_source": "Kaldır", + "add_download_source": "Kaynak ekle", + "download_count_zero": "İndirme seçeneği yok", + "download_count_one": "{{countFormatted}} indirme seçeneği", + "download_count_other": "{{countFormatted}} indirme seçeneği", + "download_source_url": "İndirme kaynağı URL'si", + "add_download_source_description": ".json dosyasının URL'sini girin", + "download_source_up_to_date": "Güncel", + "download_source_errored": "Hatalı", + "sync_download_sources": "Kaynakları senkronize et", + "removed_download_source": "İndirme kaynağı kaldırıldı", + "added_download_source": "İndirme kaynağı eklendi", + "download_sources_synced": "Tüm indirme kaynakları senkronize edildi", + "insert_valid_json_url": "Geçerli bir JSON URL'si girin", + "found_download_option_zero": "Hiçbir indirme seçeneği bulunamadı", + "found_download_option_one": "{{countFormatted}} indirme seçeneği bulundu", + "found_download_option_other": "{{countFormatted}} indirme seçeneği bulundu", + "import": "İçe aktar", + "public": "Herkese açık", + "private": "Gizli", + "friends_only": "Sadece arkadaşlar", + "privacy": "Gizlilik", + "profile_visibility": "Profil görünürlüğü", + "profile_visibility_description": "Profilinizi ve kütüphanenizi kimlerin görebileceğini seçin", + "required_field": "Bu alan gereklidir", + "source_already_exists": "Bu kaynak zaten eklenmiş", + "must_be_valid_url": "Kaynak geçerli bir URL olmalıdır", + "blocked_users": "Engellenen kullanıcılar", + "user_unblocked": "Kullanıcının engeli kaldırıldı", + "enable_achievement_notifications": "Bir başarı kilidi açıldığında", + "launch_minimized": "Hydra'yı küçültülmüş başlat", + "disable_nsfw_alert": "NSFW uyarısını devre dışı bırak", + "seed_after_download_complete": "İndirme tamamlandıktan sonra paylaş", + "show_hidden_achievement_description": "Gizli başarı açıklamalarını kilitlenmeden önce göster" }, "notifications": { "download_complete": "İndirme tamamlandı", - "game_ready_to_install": "{{title}} kuruluma hazır", + "game_ready_to_install": "{{title}} kurulmaya hazır", "repack_list_updated": "Repack listesi güncellendi", - "repack_count_one": "{{count}} yeni repack eklendi", - "repack_count_other": "{{count}} yeni repack eklendi" + "repack_count_one": "{{count}} repack eklendi", + "repack_count_other": "{{count}} repack eklendi", + "new_update_available": "Sürüm {{version}} mevcut", + "restart_to_install_update": "Güncellemeyi yüklemek için Hydra'yı yeniden başlatın", + "notification_achievement_unlocked_title": "{{game}} için başarı kilidi açıldı", + "notification_achievement_unlocked_body": "{{achievement}} ve diğer {{count}} başarılar açıldı" }, "system_tray": { - "open": "Hydra'yı aç", + "open": "Hydra'yı Aç", "quit": "Çık" }, "game_card": { - "no_downloads": "İndirme mevcut değil" + "no_downloads": "İndirilebilir içerik bulunmuyor" }, "binary_not_found_modal": { - "title": "Programlar yüklü değil", - "description": "Sisteminizde Wine veya Lutris çalıştırılabiliri bulunamadı", - "instructions": "Oyunları düzgün şekilde çalıştırmak için Linux distronuza bunlardan birini nasıl yükleyebileceğinize bakın" + "title": "Programlar Yüklü Değil", + "description": "Wine veya Lutris çalıştırılabilir dosyaları sisteminizde bulunamadı", + "instructions": "Oyunun normal çalışabilmesi için bunlardan herhangi birini Linux dağıtımınıza uygun şekilde nasıl kuracağınızı kontrol edin" }, "modal": { - "close": "Kapat tuşu" + "close": "Kapat düğmesi" + }, + "forms": { + "toggle_password_visibility": "Şifre görünürlüğünü değiştir" + }, + "user_profile": { + "amount_hours": "{{amount}} saat", + "amount_minutes": "{{amount}} dakika", + "last_time_played": "Son oynanma {{period}}", + "activity": "Son Etkinlik", + "library": "Kütüphane", + "total_play_time": "Toplam oynama süresi", + "no_recent_activity_title": "Hmmm… burada bir şey yok", + "no_recent_activity_description": "Son zamanlarda hiç oyun oynamamışsınız. Bunu değiştirmenin zamanı geldi!", + "display_name": "Görünen isim", + "saving": "Kaydediliyor", + "save": "Kaydet", + "edit_profile": "Profili Düzenle", + "saved_successfully": "Başarıyla kaydedildi", + "try_again": "Lütfen tekrar deneyin", + "sign_out_modal_title": "Emin misiniz?", + "cancel": "İptal", + "successfully_signed_out": "Başarıyla çıkış yapıldı", + "sign_out": "Çıkış yap", + "playing_for": "{{amount}} oynanıyor", + "sign_out_modal_text": "Kütüphaneniz mevcut hesabınıza bağlı. Çıkış yaptığınızda kütüphaneniz görünür olmayacak ve herhangi bir ilerleme kaydedilmeyecek. Çıkışa devam etmek istiyor musunuz?", + "add_friends": "Arkadaş Ekle", + "add": "Ekle", + "friend_code": "Arkadaş kodu", + "see_profile": "Profili gör", + "sending": "Gönderiliyor", + "friend_request_sent": "Arkadaşlık isteği gönderildi", + "friends": "Arkadaşlar", + "friends_list": "Arkadaş listesi", + "user_not_found": "Kullanıcı bulunamadı", + "block_user": "Kullanıcıyı engelle", + "add_friend": "Arkadaş ekle", + "request_sent": "İstek gönderildi", + "request_received": "İstek alındı", + "accept_request": "İsteği kabul et", + "ignore_request": "İsteği yok say", + "cancel_request": "İsteği iptal et", + "undo_friendship": "Arkadaşlığı sonlandır", + "request_accepted": "İstek kabul edildi", + "user_blocked_successfully": "Kullanıcı başarıyla engellendi", + "user_block_modal_text": "Bu işlem {{displayName}} adlı kullanıcıyı engelleyecek", + "blocked_users": "Engellenen kullanıcılar", + "unblock": "Engeli kaldır", + "no_friends_added": "Hiç arkadaş eklemediniz", + "pending": "Bekliyor", + "no_pending_invites": "Bekleyen davetiniz yok", + "no_blocked_users": "Engellenmiş kullanıcı yok", + "friend_code_copied": "Arkadaş kodu kopyalandı", + "undo_friendship_modal_text": "Bu işlem {{displayName}} ile arkadaşlığınızı sonlandıracak", + "privacy_hint": "Bunu kimin görebileceğini ayarlamak için <0>Ayarlar bölümüne gidin", + "locked_profile": "Bu profil gizli", + "image_process_failure": "Görüntü işleme başarısız oldu", + "required_field": "Bu alan gerekli", + "displayname_min_length": "Görünen isim en az 3 karakter uzunluğunda olmalıdır", + "displayname_max_length": "Görünen isim en fazla 50 karakter uzunluğunda olabilir", + "report_profile": "Bu profili bildir", + "report_reason": "Bu profili neden bildiriyorsunuz?", + "report_description": "Ek bilgi", + "report_description_placeholder": "Ek bilgi", + "report": "Bildir", + "report_reason_hate": "Nefret söylemi", + "report_reason_sexual_content": "Cinsel içerik", + "report_reason_violence": "Şiddet", + "report_reason_spam": "Spam", + "report_reason_other": "Diğer", + "profile_reported": "Profil bildirildi", + "your_friend_code": "Arkadaş kodunuz:", + "upload_banner": "Afiş yükle", + "uploading_banner": "Afiş yükleniyor…", + "background_image_updated": "Arka plan görüntüsü güncellendi", + "stats": "İstatistikler", + "achievements": "Başarılar", + "games": "Oyunlar", + "top_percentile": "En üst {{percentile}}%", + "ranking_updated_weekly": "Sıralama haftalık olarak güncellenir", + "playing": "{{game}} oynanıyor", + "achievements_unlocked": "Başarılar açıldı", + "earned_points": "Kazanılan puanlar", + "show_achievements_on_profile": "Başarılarınızı profilinizde gösterin", + "show_points_on_profile": "Kazandığınız puanları profilinizde gösterin" + }, + "achievement": { + "achievement_unlocked": "Başarı açıldı", + "user_achievements": "{{displayName}}'in Başarıları", + "your_achievements": "Başarılarınız", + "unlocked_at": "Açılma zamanı: {{date}}", + "subscription_needed": "Bu içeriği görmek için bir Hydra Cloud aboneliği gereklidir", + "new_achievements_unlocked": "{{gameCount}} oyundan {{achievementCount}} yeni başarı açıldı", + "achievement_progress": "{{unlockedCount}}/{{totalCount}} başarı", + "achievements_unlocked_for_game": "{{gameTitle}} oyunu için {{achievementCount}} yeni başarı açıldı", + "hidden_achievement_tooltip": "Bu gizli bir başarıdır", + "achievement_earn_points": "Bu başarı ile {{points}} puan kazanın", + "earned_points": "Kazanılan puanlar:", + "available_points": "Mevcut puanlar:", + "how_to_earn_achievements_points": "Başarı puanları nasıl kazanılır?" + }, + "hydra_cloud": { + "subscription_tour_title": "Hydra Cloud Aboneliği", + "subscribe_now": "Şimdi abone olun", + "cloud_saving": "Bulut kaydetme", + "cloud_achievements": "Başarılarınızı buluta kaydedin", + "animated_profile_picture": "Animasyonlu profil resimleri", + "premium_support": "Premium Destek", + "show_and_compare_achievements": "Başarılarınızı diğer kullanıcılarla karşılaştırın ve gösterin", + "animated_profile_banner": "Animasyonlu profil afişi", + "hydra_cloud": "Hydra Cloud", + "hydra_cloud_feature_found": "Bir Hydra Cloud özelliği keşfettiniz!", + "learn_more": "Daha Fazla Bilgi Edinin" } } + + + + + From ab2d8c351bfd02c02371b6b0a08d65450cb68b37 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 5 Jan 2025 15:37:25 -0300 Subject: [PATCH 033/291] Removing excessive new lines --- src/locales/tr/translation.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/locales/tr/translation.json b/src/locales/tr/translation.json index 8d0e7811..6fa89c03 100644 --- a/src/locales/tr/translation.json +++ b/src/locales/tr/translation.json @@ -421,8 +421,3 @@ "learn_more": "Daha Fazla Bilgi Edinin" } } - - - - - From 2a31c32cdaa571d7429692cd077e58ff6238bef9 Mon Sep 17 00:00:00 2001 From: Shisuys Date: Sun, 5 Jan 2025 17:32:09 -0300 Subject: [PATCH 034/291] Update datanodes.ts --- src/main/services/hosters/datanodes.ts | 33 +++++++++++--------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/main/services/hosters/datanodes.ts b/src/main/services/hosters/datanodes.ts index 28bd026e..d77e7d51 100644 --- a/src/main/services/hosters/datanodes.ts +++ b/src/main/services/hosters/datanodes.ts @@ -1,7 +1,7 @@ import axios, { AxiosResponse } from "axios"; export class DatanodesApi { - private static session = axios.create({}); + private static readonly session = axios.create({}); public static async getDownloadUrl(downloadUrl: string): Promise { const parsedUrl = new URL(downloadUrl); @@ -10,16 +10,6 @@ export class DatanodesApi { const fileCode = decodeURIComponent(pathSegments[1]); const fileName = decodeURIComponent(pathSegments[pathSegments.length - 1]); - const headers: Record = { - "Content-Type": "application/x-www-form-urlencoded", - Cookie: `lang=english; file_name=${fileName}; file_code=${fileCode};`, - Host: "datanodes.to", - Origin: "https://datanodes.to", - Referer: "https://datanodes.to/download", - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", - }; - const payload = new URLSearchParams({ op: "download2", id: fileCode, @@ -30,16 +20,21 @@ export class DatanodesApi { adblock_detected: "", }); - const config = { - headers, - maxRedirects: 0, - validateStatus: (status: number) => status === 302 || status < 400, - }; - - const response: AxiosResponse = await DatanodesApi.session.post( + const response: AxiosResponse = await this.session.post( "https://datanodes.to/download", payload, - config + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Cookie: `lang=english; file_name=${fileName}; file_code=${fileCode};`, + Host: "datanodes.to", + Origin: "https://datanodes.to", + Referer: "https://datanodes.to/download", + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", + }, + maxRedirects: 0, validateStatus: (status: number) => status === 302 || status < 400, + } ); if (response.status === 302) { From cac2a7a70e3266992069026e6d30ecee55d24950 Mon Sep 17 00:00:00 2001 From: hydrasources Date: Tue, 7 Jan 2025 18:53:34 +0300 Subject: [PATCH 035/291] Update translation.json --- src/locales/ru/translation.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 741f5e16..c513afee 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -167,6 +167,9 @@ "loading_save_preview": "Поиск сохранений…", "wine_prefix": "Префикс Wine", "wine_prefix_description": "Префикс Wine, используемый для запуска этой игры", + "launch_options": "Параметры запуска", + "launch_options_description": "Опытные пользователи могут внести изменения в параметры запуска", + "launch_options_placeholder": "Параметр не указан ", "no_download_option_info": "Информация недоступна", "backup_deletion_failed": "Не удалось удалить резервную копию", "max_number_of_artifacts_reached": "Достигнуто максимальное количество резервных копий для этой игры", @@ -175,7 +178,11 @@ "select_folder": "Выбрать папку", "backup_from": "Резервная копия от {{date}}", "custom_backup_location_set": "Установлено настраиваемое местоположение резервной копии", - "no_directory_selected": "Не выбран каталог" + "no_directory_selected": "Не выбран каталог", + "no_write_permission": "Невозможно загрузить в эту директорию. Нажмите здесь, чтобы узнать больше." + "reset_achievements_title": "Вы уверены?", + "reset_achievements_success": "Достижения успешно сброшены", + "reset_achievements_error": "Не удалось сбросить достижения". }, "activation": { "title": "Активировать Hydra", From 317434f663fb61114b419adc8a67a30c700374c9 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Tue, 7 Jan 2025 13:02:20 -0300 Subject: [PATCH 036/291] fix: lint error --- src/locales/ru/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index c513afee..3bb32440 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -182,7 +182,7 @@ "no_write_permission": "Невозможно загрузить в эту директорию. Нажмите здесь, чтобы узнать больше." "reset_achievements_title": "Вы уверены?", "reset_achievements_success": "Достижения успешно сброшены", - "reset_achievements_error": "Не удалось сбросить достижения". + "reset_achievements_error": "Не удалось сбросить достижения" }, "activation": { "title": "Активировать Hydra", From a0a3697516c521bb8fa81cc5ef85e18312a70ae2 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Tue, 7 Jan 2025 13:08:13 -0300 Subject: [PATCH 037/291] fix: missing comma --- src/locales/ru/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 3bb32440..d02d7797 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -179,7 +179,7 @@ "backup_from": "Резервная копия от {{date}}", "custom_backup_location_set": "Установлено настраиваемое местоположение резервной копии", "no_directory_selected": "Не выбран каталог", - "no_write_permission": "Невозможно загрузить в эту директорию. Нажмите здесь, чтобы узнать больше." + "no_write_permission": "Невозможно загрузить в эту директорию. Нажмите здесь, чтобы узнать больше.", "reset_achievements_title": "Вы уверены?", "reset_achievements_success": "Достижения успешно сброшены", "reset_achievements_error": "Не удалось сбросить достижения" From 21a88b889f1a87d766c909ed7ed56cfe574f23ef Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Tue, 7 Jan 2025 13:45:31 -0300 Subject: [PATCH 038/291] chore: run prettier --- src/locales/ru/translation.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index d02d7797..92008a5e 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -167,8 +167,8 @@ "loading_save_preview": "Поиск сохранений…", "wine_prefix": "Префикс Wine", "wine_prefix_description": "Префикс Wine, используемый для запуска этой игры", - "launch_options": "Параметры запуска", - "launch_options_description": "Опытные пользователи могут внести изменения в параметры запуска", + "launch_options": "Параметры запуска", + "launch_options_description": "Опытные пользователи могут внести изменения в параметры запуска", "launch_options_placeholder": "Параметр не указан ", "no_download_option_info": "Информация недоступна", "backup_deletion_failed": "Не удалось удалить резервную копию", From 8fbe23e61cfcba955d148fd0d9df810e6da0daf1 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Thu, 9 Jan 2025 00:58:25 -0300 Subject: [PATCH 039/291] readme translation pt-BR improvement --- docs/README.pt-BR.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/README.pt-BR.md b/docs/README.pt-BR.md index f9ba9d66..6e65d3fb 100644 --- a/docs/README.pt-BR.md +++ b/docs/README.pt-BR.md @@ -125,6 +125,10 @@ cd hydra yarn ``` +### Install OpenSSL 1.1 + +[OpenSSL 1.1](https://slproweb.com/download/Win64OpenSSL-1_1_1w.exe) é exigido pelo libtorrent em ambientes Windows. + ### Instale Python 3.9 Certifique-se de ter o Python 3.9 instalado em sua máquina. Você pode baixá-lo e instalá-lo em [python.org](https://www.python.org/downloads/release/python-3913/). From 392279c4e1d47f190026b62b3075a46f26799e1e Mon Sep 17 00:00:00 2001 From: Kelvin <90298223+KelvinDiasMoreira@users.noreply.github.com> Date: Thu, 9 Jan 2025 11:58:08 -0300 Subject: [PATCH 040/291] fix translate error --- docs/README.pt-BR.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.pt-BR.md b/docs/README.pt-BR.md index 6e65d3fb..ca5933fb 100644 --- a/docs/README.pt-BR.md +++ b/docs/README.pt-BR.md @@ -125,7 +125,7 @@ cd hydra yarn ``` -### Install OpenSSL 1.1 +### Instale OpenSSL 1.1 [OpenSSL 1.1](https://slproweb.com/download/Win64OpenSSL-1_1_1w.exe) é exigido pelo libtorrent em ambientes Windows. From 09c1170407670385dc847648e8c49ed4b25322bb Mon Sep 17 00:00:00 2001 From: Kelvin <90298223+KelvinDiasMoreira@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:45:19 -0300 Subject: [PATCH 041/291] added anchor tag --- docs/README.pt-BR.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.pt-BR.md b/docs/README.pt-BR.md index ca5933fb..ca942854 100644 --- a/docs/README.pt-BR.md +++ b/docs/README.pt-BR.md @@ -125,7 +125,7 @@ cd hydra yarn ``` -### Instale OpenSSL 1.1 +### Instale OpenSSL 1.1 [OpenSSL 1.1](https://slproweb.com/download/Win64OpenSSL-1_1_1w.exe) é exigido pelo libtorrent em ambientes Windows. From abb16e77364b650b7c232aa4c2dd9941402b186d Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 11 Jan 2025 15:19:20 -0300 Subject: [PATCH 042/291] chore: prettier --- src/main/services/hosters/datanodes.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/services/hosters/datanodes.ts b/src/main/services/hosters/datanodes.ts index d77e7d51..ae144418 100644 --- a/src/main/services/hosters/datanodes.ts +++ b/src/main/services/hosters/datanodes.ts @@ -33,7 +33,8 @@ export class DatanodesApi { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", }, - maxRedirects: 0, validateStatus: (status: number) => status === 302 || status < 400, + maxRedirects: 0, + validateStatus: (status: number) => status === 302 || status < 400, } ); From db2e31b8ccfd5b23f33bac0f3e6f77fd666ccfbd Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 11 Jan 2025 17:52:48 -0300 Subject: [PATCH 043/291] feat: add torbox migration --- src/main/entity/user-preferences.entity.ts | 3 +++ src/main/knex-client.ts | 2 ++ src/main/main.ts | 6 ++++-- ...0250111182229_add_torbox_api_token_column.ts | 17 +++++++++++++++++ src/main/services/download/torbox.ts | 2 +- src/types/index.ts | 1 + 6 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 src/main/migrations/20250111182229_add_torbox_api_token_column.ts diff --git a/src/main/entity/user-preferences.entity.ts b/src/main/entity/user-preferences.entity.ts index a850b42f..109ede5f 100644 --- a/src/main/entity/user-preferences.entity.ts +++ b/src/main/entity/user-preferences.entity.ts @@ -20,6 +20,9 @@ export class UserPreferences { @Column("text", { nullable: true }) realDebridApiToken: string | null; + @Column("text", { nullable: true }) + torBoxApiToken: string | null; + @Column("boolean", { default: false }) downloadNotificationsEnabled: boolean; diff --git a/src/main/knex-client.ts b/src/main/knex-client.ts index 821efc80..c816c7c7 100644 --- a/src/main/knex-client.ts +++ b/src/main/knex-client.ts @@ -17,6 +17,7 @@ import { AddShouldSeedColumn } from "./migrations/20241108200154_add_should_seed import { AddSeedAfterDownloadColumn } from "./migrations/20241108201806_add_seed_after_download"; import { AddHiddenAchievementDescriptionColumn } from "./migrations/20241216140633_add_hidden_achievement_description_column "; import { AddLaunchOptionsColumnToGame } from "./migrations/20241226044022_add_launch_options_column_to_game"; +import { AddTorBoxApiToken } from "./migrations/20250111182229_add_torbox_api_token_column"; export type HydraMigration = Knex.Migration & { name: string }; @@ -39,6 +40,7 @@ class MigrationSource implements Knex.MigrationSource { AddSeedAfterDownloadColumn, AddHiddenAchievementDescriptionColumn, AddLaunchOptionsColumnToGame, + AddTorBoxApiToken, ]); } getMigrationName(migration: HydraMigration): string { diff --git a/src/main/main.ts b/src/main/main.ts index 81916174..d27f0cbd 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -19,10 +19,12 @@ const loadState = async (userPreferences: UserPreferences | null) => { Aria2.spawn(); if (userPreferences?.realDebridApiToken) { - RealDebridClient.authorize(userPreferences?.realDebridApiToken); + RealDebridClient.authorize(userPreferences.realDebridApiToken); } - TorBoxClient.authorize("7371d5ec-52fa-4b87-9052-0c8c96d947cc"); + if (userPreferences?.torBoxApiToken) { + TorBoxClient.authorize(userPreferences?.torBoxApiToken); + } Ludusavi.addManifestToLudusaviConfig(); diff --git a/src/main/migrations/20250111182229_add_torbox_api_token_column.ts b/src/main/migrations/20250111182229_add_torbox_api_token_column.ts new file mode 100644 index 00000000..fc1904fd --- /dev/null +++ b/src/main/migrations/20250111182229_add_torbox_api_token_column.ts @@ -0,0 +1,17 @@ +import type { HydraMigration } from "@main/knex-client"; +import type { Knex } from "knex"; + +export const AddTorBoxApiToken: HydraMigration = { + name: "AddTorBoxApiToken", + up: (knex: Knex) => { + return knex.schema.alterTable("user_preferences", (table) => { + return table.string("torBoxApiToken").nullable(); + }); + }, + + down: async (knex: Knex) => { + return knex.schema.alterTable("user_preferences", (table) => { + return table.dropColumn("torBoxApiToken"); + }); + }, +}; diff --git a/src/main/services/download/torbox.ts b/src/main/services/download/torbox.ts index 7e0c9089..f0af52eb 100644 --- a/src/main/services/download/torbox.ts +++ b/src/main/services/download/torbox.ts @@ -19,7 +19,7 @@ export class TorBoxClient { Authorization: `Bearer ${apiToken}`, }, }); - this.apiToken = "7371d5ec-52fa-4b87-9052-0c8c96d947cc"; + this.apiToken = apiToken; } static async addMagnet(magnet: string) { diff --git a/src/types/index.ts b/src/types/index.ts index 345893a5..92cc566e 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -163,6 +163,7 @@ export interface UserPreferences { repackUpdatesNotificationsEnabled: boolean; achievementNotificationsEnabled: boolean; realDebridApiToken: string | null; + torboxApiToken: string | null; preferQuitInsteadOfHiding: boolean; runAtStartup: boolean; startMinimized: boolean; From b1dde446b2cb6507311b5a6d019bb71baa1606bf Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 12 Jan 2025 11:00:20 -0300 Subject: [PATCH 044/291] feat: few adjustments --- src/main/services/download/torbox.ts | 12 ++++++++---- src/types/index.ts | 2 +- src/types/torbox.types.ts | 4 ++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/services/download/torbox.ts b/src/main/services/download/torbox.ts index f0af52eb..b0d339dd 100644 --- a/src/main/services/download/torbox.ts +++ b/src/main/services/download/torbox.ts @@ -10,19 +10,19 @@ import type { export class TorBoxClient { private static instance: AxiosInstance; private static readonly baseURL = "https://api.torbox.app/v1/api"; - public static apiToken: string; + private static apiToken: string; static authorize(apiToken: string) { + this.apiToken = apiToken; this.instance = axios.create({ baseURL: this.baseURL, headers: { Authorization: `Bearer ${apiToken}`, }, }); - this.apiToken = apiToken; } - static async addMagnet(magnet: string) { + private static async addMagnet(magnet: string) { const form = new FormData(); form.append("magnet", magnet); @@ -31,6 +31,10 @@ export class TorBoxClient { form ); + if (!response.data.success) { + throw new Error(response.data.detail); + } + return response.data.data; } @@ -74,7 +78,7 @@ export class TorBoxClient { return response.data.data; } - static async getTorrentIdAndName(magnetUri: string) { + private static async getTorrentIdAndName(magnetUri: string) { const userTorrents = await this.getAllTorrentsFromUser(); const { infoHash } = await parseTorrent(magnetUri); diff --git a/src/types/index.ts b/src/types/index.ts index 92cc566e..b6fcbbb4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -163,7 +163,7 @@ export interface UserPreferences { repackUpdatesNotificationsEnabled: boolean; achievementNotificationsEnabled: boolean; realDebridApiToken: string | null; - torboxApiToken: string | null; + torBoxApiToken: string | null; preferQuitInsteadOfHiding: boolean; runAtStartup: boolean; startMinimized: boolean; diff --git a/src/types/torbox.types.ts b/src/types/torbox.types.ts index 51e8bd12..ee72600a 100644 --- a/src/types/torbox.types.ts +++ b/src/types/torbox.types.ts @@ -54,14 +54,14 @@ export interface TorBoxTorrentInfo { export interface TorBoxTorrentInfoRequest { success: boolean; detail: string; - error: string; + error: string | null; data: TorBoxTorrentInfo[]; } export interface TorBoxAddTorrentRequest { success: boolean; detail: string; - error: string; + error: string | null; data: { torrent_id: number; name: string; From 87613023842e0e412210022fc5d499f250b06dcf Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 12 Jan 2025 11:01:18 -0300 Subject: [PATCH 045/291] feat: debrid token input component --- src/locales/en/translation.json | 3 +- src/locales/pt-BR/translation.json | 3 +- .../pages/settings/settings-debrid-input.tsx | 186 ++++++++++++++++++ ...l-debrid.css.ts => settings-debrid.css.ts} | 0 ...gs-real-debrid.tsx => settings-debrid.tsx} | 66 ++++++- src/renderer/src/pages/settings/settings.tsx | 6 +- src/types/index.ts | 2 + 7 files changed, 252 insertions(+), 14 deletions(-) create mode 100644 src/renderer/src/pages/settings/settings-debrid-input.tsx rename src/renderer/src/pages/settings/{settings-real-debrid.css.ts => settings-debrid.css.ts} (100%) rename src/renderer/src/pages/settings/{settings-real-debrid.tsx => settings-debrid.tsx} (65%) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 4e3dcb37..233d04e4 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -280,7 +280,8 @@ "launch_minimized": "Launch Hydra minimized", "disable_nsfw_alert": "Disable NSFW alert", "seed_after_download_complete": "Seed after download complete", - "show_hidden_achievement_description": "Show hidden achievements description before unlocking them" + "show_hidden_achievement_description": "Show hidden achievements description before unlocking them", + "debrid_services": "Debrid Services" }, "notifications": { "download_complete": "Download complete", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 2a80084f..453aff7c 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -268,7 +268,8 @@ "launch_minimized": "Iniciar o Hydra minimizado", "disable_nsfw_alert": "Desativar alerta de conteúdo inapropriado", "seed_after_download_complete": "Semear após a conclusão do download", - "show_hidden_achievement_description": "Mostrar descrição de conquistas ocultas antes de debloqueá-las" + "show_hidden_achievement_description": "Mostrar descrição de conquistas ocultas antes de debloqueá-las", + "debrid_services": "Serviços Debrid" }, "notifications": { "download_complete": "Download concluído", diff --git a/src/renderer/src/pages/settings/settings-debrid-input.tsx b/src/renderer/src/pages/settings/settings-debrid-input.tsx new file mode 100644 index 00000000..5223e378 --- /dev/null +++ b/src/renderer/src/pages/settings/settings-debrid-input.tsx @@ -0,0 +1,186 @@ +import { useContext, useEffect, useMemo, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Button, CheckboxField, Link, TextField } from "@renderer/components"; +import * as styles from "./settings-debrid.css"; +import { useAppSelector, useToast } from "@renderer/hooks"; +import { SPACING_UNIT } from "@renderer/theme.css"; +import { settingsContext } from "@renderer/context"; +import { DebridServices } from "@types"; + +const REAL_DEBRID_API_TOKEN_URL = "https://real-debrid.com/apitoken"; +const TORBOX_API_TOKEN_URL = "https://torbox.app/settings"; + +interface SettingsDebridForm { + useRealDebrid: boolean; + realDebridApiToken: string | null; + useTorBox: boolean; + torBoxApiToken: string | null; +} + +export interface SettingsDebridProps { + service: DebridServices; + form: SettingsDebridForm; + setForm: (SettingsDebridForm) => void; +} + +export function SettingsDebridInput({ + service, + form, + setForm, +}: SettingsDebridProps) { + const userPreferences = useAppSelector( + (state) => state.userPreferences.value + ); + + const { updateUserPreferences } = useContext(settingsContext); + + const [isLoading, setIsLoading] = useState(false); + + const { showSuccessToast, showErrorToast } = useToast(); + + const { t } = useTranslation("settings"); + + useEffect(() => { + if (userPreferences) { + setForm({ + useRealDebrid: Boolean(userPreferences.realDebridApiToken), + realDebridApiToken: userPreferences.realDebridApiToken ?? null, + useTorBox: Boolean(userPreferences.torBoxApiToken), + torBoxApiToken: userPreferences.torBoxApiToken ?? null, + }); + } + }, [userPreferences]); + + const handleFormSubmit: React.FormEventHandler = async ( + event + ) => { + setIsLoading(true); + event.preventDefault(); + + try { + if (form.useRealDebrid) { + const user = await window.electron.authenticateRealDebrid( + form.realDebridApiToken! + ); + + if (user.type === "free") { + showErrorToast( + t("real_debrid_free_account_error", { username: user.username }) + ); + + return; + } else { + showSuccessToast( + t("real_debrid_linked_message", { username: user.username }) + ); + } + } else { + showSuccessToast(t("changes_saved")); + } + + updateUserPreferences({ + realDebridApiToken: form.useRealDebrid ? form.realDebridApiToken : null, + }); + } catch (err) { + showErrorToast(t("real_debrid_invalid_token")); + } finally { + setIsLoading(false); + } + }; + + const useDebridService = useMemo(() => { + if (service === "RealDebrid") { + return form.useRealDebrid; + } + + if (service === "TorBox") { + return form.useTorBox; + } + + return false; + }, [form, service]); + + const debridApiToken = useMemo(() => { + if (service === "RealDebrid") { + return form.realDebridApiToken; + } + + if (service === "TorBox") { + return form.torBoxApiToken; + } + + return null; + }, [form, service]); + + const onChangeCheckbox = () => { + if (service === "RealDebrid") { + setForm((prev) => ({ + ...prev, + useRealDebrid: !form.useRealDebrid, + })); + } + + if (service === "TorBox") { + setForm((prev) => ({ + ...prev, + useTorBox: !form.useTorBox, + })); + } + }; + + const onChangeInput = (event: React.ChangeEvent) => { + if (service === "RealDebrid") { + setForm((prev) => ({ + ...prev, + realDebridApiToken: event.target.value, + })); + } + + if (service === "TorBox") { + setForm((prev) => ({ + ...prev, + torBoxApiToken: event.target.value, + })); + } + }; + + const isButtonDisabled = + (form.useRealDebrid && !form.realDebridApiToken) || isLoading; + + return ( +
+ + + {useDebridService && ( + + {t("save")} + + } + hint={ + + + + } + /> + )} + + ); +} diff --git a/src/renderer/src/pages/settings/settings-real-debrid.css.ts b/src/renderer/src/pages/settings/settings-debrid.css.ts similarity index 100% rename from src/renderer/src/pages/settings/settings-real-debrid.css.ts rename to src/renderer/src/pages/settings/settings-debrid.css.ts diff --git a/src/renderer/src/pages/settings/settings-real-debrid.tsx b/src/renderer/src/pages/settings/settings-debrid.tsx similarity index 65% rename from src/renderer/src/pages/settings/settings-real-debrid.tsx rename to src/renderer/src/pages/settings/settings-debrid.tsx index 35804664..9d63e68a 100644 --- a/src/renderer/src/pages/settings/settings-real-debrid.tsx +++ b/src/renderer/src/pages/settings/settings-debrid.tsx @@ -2,7 +2,7 @@ import { useContext, useEffect, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; import { Button, CheckboxField, Link, TextField } from "@renderer/components"; -import * as styles from "./settings-real-debrid.css"; +import * as styles from "./settings-debrid.css"; import { useAppSelector, useToast } from "@renderer/hooks"; @@ -10,8 +10,9 @@ import { SPACING_UNIT } from "@renderer/theme.css"; import { settingsContext } from "@renderer/context"; const REAL_DEBRID_API_TOKEN_URL = "https://real-debrid.com/apitoken"; +const TORBOX_API_TOKEN_URL = "https://torbox.app/settings"; -export function SettingsRealDebrid() { +export function SettingsDebrid() { const userPreferences = useAppSelector( (state) => state.userPreferences.value ); @@ -22,6 +23,8 @@ export function SettingsRealDebrid() { const [form, setForm] = useState({ useRealDebrid: false, realDebridApiToken: null as string | null, + useTorBox: false, + torBoxApiToken: null as string | null, }); const { showSuccessToast, showErrorToast } = useToast(); @@ -33,6 +36,8 @@ export function SettingsRealDebrid() { setForm({ useRealDebrid: Boolean(userPreferences.realDebridApiToken), realDebridApiToken: userPreferences.realDebridApiToken ?? null, + useTorBox: Boolean(userPreferences.torBoxApiToken), + torBoxApiToken: userPreferences.torBoxApiToken ?? null, }); } }, [userPreferences]); @@ -102,6 +107,17 @@ export function SettingsRealDebrid() { } placeholder="API Token" containerProps={{ style: { marginTop: `${SPACING_UNIT}px` } }} + rightContent={ + + } hint={ @@ -110,13 +126,45 @@ export function SettingsRealDebrid() { /> )} - + + setForm((prev) => ({ + ...prev, + useTorBox: !form.useTorBox, + })) + } + /> + + {form.useTorBox && ( + + setForm({ ...form, torBoxApiToken: event.target.value }) + } + placeholder="API Token" + containerProps={{ style: { marginTop: `${SPACING_UNIT}px` } }} + rightContent={ + + } + hint={ + + + + } + /> + )} ); } diff --git a/src/renderer/src/pages/settings/settings.tsx b/src/renderer/src/pages/settings/settings.tsx index dffdfbae..00ceebd7 100644 --- a/src/renderer/src/pages/settings/settings.tsx +++ b/src/renderer/src/pages/settings/settings.tsx @@ -2,7 +2,7 @@ import { Button } from "@renderer/components"; import * as styles from "./settings.css"; import { useTranslation } from "react-i18next"; -import { SettingsRealDebrid } from "./settings-real-debrid"; +import { SettingsDebrid } from "./settings-debrid"; import { SettingsGeneral } from "./settings-general"; import { SettingsBehavior } from "./settings-behavior"; @@ -25,7 +25,7 @@ export default function Settings() { t("general"), t("behavior"), t("download_sources"), - "Real-Debrid", + t("debrid_services"), ]; if (userDetails) return [...categories, t("privacy")]; @@ -50,7 +50,7 @@ export default function Settings() { } if (currentCategoryIndex === 3) { - return ; + return ; } return ; diff --git a/src/types/index.ts b/src/types/index.ts index b6fcbbb4..da5deea4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -19,6 +19,8 @@ export type HydraCloudFeature = | "backup" | "achievements-points"; +export type DebridServices = "RealDebrid" | "TorBox"; + export interface GameRepack { id: number; title: string; From b4014535e8987c95e7d9183eee77b95f8bf30c57 Mon Sep 17 00:00:00 2001 From: Shisuys Date: Mon, 13 Jan 2025 10:23:50 -0300 Subject: [PATCH 046/291] change pixeldrain url to gamedrivers servers --- src/main/services/download/download-manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index 134a74e6..8fda319e 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -263,7 +263,7 @@ export class DownloadManager { return { action: "start", game_id: game.id, - url: `https://pixeldrain.com/api/file/${id}?download`, + url: `https://cdn.pd5-gamedriveorg.workers.dev/api/file/${id}`, save_path: game.downloadPath!, }; } From 2c5fb8a0379c1515a3f2b4874ea5ec23c7bc8344 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 15 Jan 2025 16:58:59 +0000 Subject: [PATCH 047/291] feat: adding initial leveldb configuration --- electron.vite.config.ts | 7 ++ package.json | 1 + src/main/constants.ts | 7 +- src/main/data-source.ts | 4 -- src/main/entity/index.ts | 2 - src/main/entity/user-auth.entity.ts | 45 ------------ src/main/entity/user-subscription.entity.ts | 42 ------------ src/main/events/auth/get-session-hash.ts | 13 +++- src/main/events/auth/sign-out.ts | 21 +++--- src/main/events/misc/open-checkout.ts | 14 ++-- src/main/events/user/get-user-friends.ts | 11 +-- src/main/repository.ts | 7 -- src/main/services/hydra-api.ts | 71 ++++++++++++------- src/main/services/index.ts | 1 + src/main/services/user/get-user-data.ts | 64 +++++++---------- src/renderer/src/components/modal/modal.tsx | 1 + src/types/index.ts | 11 +-- yarn.lock | 76 +++++++++++++++++++++ 18 files changed, 202 insertions(+), 196 deletions(-) delete mode 100644 src/main/entity/user-auth.entity.ts delete mode 100644 src/main/entity/user-subscription.entity.ts diff --git a/electron.vite.config.ts b/electron.vite.config.ts index cd08b6d4..2b7048c4 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -38,6 +38,13 @@ export default defineConfig(({ mode }) => { build: { sourcemap: true, }, + css: { + preprocessorOptions: { + scss: { + api: "modern", + }, + }, + }, resolve: { alias: { "@renderer": resolve("src/renderer/src"), diff --git a/package.json b/package.json index 2895f20c..4630ad41 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "jsdom": "^24.0.0", "jsonwebtoken": "^9.0.2", "knex": "^3.1.0", + "level": "^9.0.0", "lodash-es": "^4.17.21", "parse-torrent": "^11.0.17", "piscina": "^4.7.0", diff --git a/src/main/constants.ts b/src/main/constants.ts index b98b5935..f9d9c3e2 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -7,13 +7,18 @@ export const defaultDownloadsPath = app.getPath("downloads"); export const isStaging = import.meta.env.MAIN_VITE_API_URL.includes("staging"); +export const levelDatabasePath = path.join( + app.getPath("userData"), + `hydra-db${isStaging ? "-staging" : ""}` +); + export const databaseDirectory = path.join(app.getPath("appData"), "hydra"); export const databasePath = path.join( databaseDirectory, isStaging ? "hydra_test.db" : "hydra.db" ); -export const logsPath = path.join(app.getPath("appData"), "hydra", "logs"); +export const logsPath = path.join(app.getPath("userData"), "hydra", "logs"); export const seedsPath = app.isPackaged ? path.join(process.resourcesPath, "seeds") diff --git a/src/main/data-source.ts b/src/main/data-source.ts index 51c8522e..05fdb04d 100644 --- a/src/main/data-source.ts +++ b/src/main/data-source.ts @@ -4,9 +4,7 @@ import { Game, GameShopCache, UserPreferences, - UserAuth, GameAchievement, - UserSubscription, } from "@main/entity"; import { databasePath } from "./constants"; @@ -15,9 +13,7 @@ export const dataSource = new DataSource({ type: "better-sqlite3", entities: [ Game, - UserAuth, UserPreferences, - UserSubscription, GameShopCache, DownloadQueue, GameAchievement, diff --git a/src/main/entity/index.ts b/src/main/entity/index.ts index 1625ac8a..ab0ebff9 100644 --- a/src/main/entity/index.ts +++ b/src/main/entity/index.ts @@ -1,7 +1,5 @@ export * from "./game.entity"; -export * from "./user-auth.entity"; export * from "./user-preferences.entity"; -export * from "./user-subscription.entity"; export * from "./game-shop-cache.entity"; export * from "./game.entity"; export * from "./game-achievements.entity"; diff --git a/src/main/entity/user-auth.entity.ts b/src/main/entity/user-auth.entity.ts deleted file mode 100644 index f34e23ec..00000000 --- a/src/main/entity/user-auth.entity.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - UpdateDateColumn, - OneToOne, -} from "typeorm"; -import { UserSubscription } from "./user-subscription.entity"; - -@Entity("user_auth") -export class UserAuth { - @PrimaryGeneratedColumn() - id: number; - - @Column("text", { default: "" }) - userId: string; - - @Column("text", { default: "" }) - displayName: string; - - @Column("text", { nullable: true }) - profileImageUrl: string | null; - - @Column("text", { nullable: true }) - backgroundImageUrl: string | null; - - @Column("text", { default: "" }) - accessToken: string; - - @Column("text", { default: "" }) - refreshToken: string; - - @Column("int", { default: 0 }) - tokenExpirationTimestamp: number; - - @OneToOne("UserSubscription", "user") - subscription: UserSubscription | null; - - @CreateDateColumn() - createdAt: Date; - - @UpdateDateColumn() - updatedAt: Date; -} diff --git a/src/main/entity/user-subscription.entity.ts b/src/main/entity/user-subscription.entity.ts deleted file mode 100644 index e74ada48..00000000 --- a/src/main/entity/user-subscription.entity.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { SubscriptionStatus } from "@types"; -import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - UpdateDateColumn, - OneToOne, - JoinColumn, -} from "typeorm"; -import { UserAuth } from "./user-auth.entity"; - -@Entity("user_subscription") -export class UserSubscription { - @PrimaryGeneratedColumn() - id: number; - - @Column("text", { default: "" }) - subscriptionId: string; - - @OneToOne("UserAuth", "subscription") - @JoinColumn() - user: UserAuth; - - @Column("text", { default: "" }) - status: SubscriptionStatus; - - @Column("text", { default: "" }) - planId: string; - - @Column("text", { default: "" }) - planName: string; - - @Column("datetime", { nullable: true }) - expiresAt: Date | null; - - @CreateDateColumn() - createdAt: Date; - - @UpdateDateColumn() - updatedAt: Date; -} diff --git a/src/main/events/auth/get-session-hash.ts b/src/main/events/auth/get-session-hash.ts index c9dd39cc..293fb62e 100644 --- a/src/main/events/auth/get-session-hash.ts +++ b/src/main/events/auth/get-session-hash.ts @@ -1,13 +1,20 @@ import jwt from "jsonwebtoken"; -import { userAuthRepository } from "@main/repository"; import { registerEvent } from "../register-event"; +import { db } from "@main/level"; +import type { Auth } from "@types"; +import { levelKeys } from "@main/level/sublevels/keys"; +import { Crypto } from "@main/services"; const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => { - const auth = await userAuthRepository.findOne({ where: { id: 1 } }); + const auth = await db.get(levelKeys.auth, { + valueEncoding: "json", + }); if (!auth) return null; - const payload = jwt.decode(auth.accessToken) as jwt.JwtPayload; + const payload = jwt.decode( + Crypto.decrypt(auth.accessToken) + ) as jwt.JwtPayload; if (!payload) return null; diff --git a/src/main/events/auth/sign-out.ts b/src/main/events/auth/sign-out.ts index 6b720015..1fb3a054 100644 --- a/src/main/events/auth/sign-out.ts +++ b/src/main/events/auth/sign-out.ts @@ -1,8 +1,10 @@ import { registerEvent } from "../register-event"; import { DownloadManager, HydraApi, gamesPlaytime } from "@main/services"; import { dataSource } from "@main/data-source"; -import { DownloadQueue, Game, UserAuth, UserSubscription } from "@main/entity"; +import { DownloadQueue, Game } from "@main/entity"; import { PythonRPC } from "@main/services/python-rpc"; +import { db } from "@main/level"; +import { levelKeys } from "@main/level/sublevels/keys"; const signOut = async (_event: Electron.IpcMainInvokeEvent) => { const databaseOperations = dataSource @@ -11,13 +13,16 @@ const signOut = async (_event: Electron.IpcMainInvokeEvent) => { await transactionalEntityManager.getRepository(Game).delete({}); - await transactionalEntityManager - .getRepository(UserAuth) - .delete({ id: 1 }); - - await transactionalEntityManager - .getRepository(UserSubscription) - .delete({ id: 1 }); + await db.batch([ + { + type: "del", + key: levelKeys.auth, + }, + { + type: "del", + key: levelKeys.user, + }, + ]); }) .then(() => { /* Removes all games being played */ diff --git a/src/main/events/misc/open-checkout.ts b/src/main/events/misc/open-checkout.ts index ba48f03b..76e7fe09 100644 --- a/src/main/events/misc/open-checkout.ts +++ b/src/main/events/misc/open-checkout.ts @@ -1,17 +1,21 @@ import { shell } from "electron"; import { registerEvent } from "../register-event"; -import { userAuthRepository } from "@main/repository"; -import { HydraApi } from "@main/services"; +import { Crypto, HydraApi } from "@main/services"; +import { db } from "@main/level"; +import type { Auth } from "@types"; +import { levelKeys } from "@main/level/sublevels/keys"; const openCheckout = async (_event: Electron.IpcMainInvokeEvent) => { - const userAuth = await userAuthRepository.findOne({ where: { id: 1 } }); + const auth = await db.get(levelKeys.auth, { + valueEncoding: "json", + }); - if (!userAuth) { + if (!auth) { return; } const paymentToken = await HydraApi.post("/auth/payment", { - refreshToken: userAuth.refreshToken, + refreshToken: Crypto.decrypt(auth.refreshToken), }).then((response) => response.accessToken); const params = new URLSearchParams({ diff --git a/src/main/events/user/get-user-friends.ts b/src/main/events/user/get-user-friends.ts index 9a6f156c..7c308506 100644 --- a/src/main/events/user/get-user-friends.ts +++ b/src/main/events/user/get-user-friends.ts @@ -1,16 +1,19 @@ -import { userAuthRepository } from "@main/repository"; +import { db } from "@main/level"; import { registerEvent } from "../register-event"; import { HydraApi } from "@main/services"; -import type { UserFriends } from "@types"; +import type { User, UserFriends } from "@types"; +import { levelKeys } from "@main/level/sublevels/keys"; export const getUserFriends = async ( userId: string, take: number, skip: number ): Promise => { - const loggedUser = await userAuthRepository.findOne({ where: { id: 1 } }); + const user = await db.get(levelKeys.user, { + valueEncoding: "json", + }); - if (loggedUser?.userId === userId) { + if (user?.id === userId) { return HydraApi.get(`/profile/friends`, { take, skip }); } diff --git a/src/main/repository.ts b/src/main/repository.ts index e0c4204e..ef120f7e 100644 --- a/src/main/repository.ts +++ b/src/main/repository.ts @@ -4,9 +4,7 @@ import { Game, GameShopCache, UserPreferences, - UserAuth, GameAchievement, - UserSubscription, } from "@main/entity"; export const gameRepository = dataSource.getRepository(Game); @@ -18,10 +16,5 @@ export const gameShopCacheRepository = dataSource.getRepository(GameShopCache); export const downloadQueueRepository = dataSource.getRepository(DownloadQueue); -export const userAuthRepository = dataSource.getRepository(UserAuth); - -export const userSubscriptionRepository = - dataSource.getRepository(UserSubscription); - export const gameAchievementRepository = dataSource.getRepository(GameAchievement); diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 63dd9b16..6cf9a8af 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -1,7 +1,3 @@ -import { - userAuthRepository, - userSubscriptionRepository, -} from "@main/repository"; import axios, { AxiosError, AxiosInstance } from "axios"; import { WindowManager } from "./window-manager"; import url from "url"; @@ -13,6 +9,10 @@ import { omit } from "lodash-es"; import { appVersion } from "@main/constants"; import { getUserData } from "./user/get-user-data"; import { isFuture, isToday } from "date-fns"; +import { db } from "@main/level"; +import { levelKeys } from "@main/level/sublevels/keys"; +import type { Auth, User } from "@types"; +import { Crypto } from "./crypto"; interface HydraApiOptions { needsAuth?: boolean; @@ -77,14 +77,14 @@ export class HydraApi { tokenExpirationTimestamp ); - await userAuthRepository.upsert( + db.put( + levelKeys.auth, { - id: 1, - accessToken, + accessToken: Crypto.encrypt(accessToken), + refreshToken: Crypto.encrypt(refreshToken), tokenExpirationTimestamp, - refreshToken, }, - ["id"] + { valueEncoding: "json" } ); await getUserData().then((userDetails) => { @@ -186,17 +186,23 @@ export class HydraApi { ); } - const userAuth = await userAuthRepository.findOne({ - where: { id: 1 }, - relations: { subscription: true }, + const result = await db.getMany([levelKeys.auth, levelKeys.user], { + valueEncoding: "json", }); + const userAuth = result.at(0) as Auth | undefined; + const user = result.at(1) as User | undefined; + this.userAuth = { - authToken: userAuth?.accessToken ?? "", - refreshToken: userAuth?.refreshToken ?? "", + authToken: userAuth?.accessToken + ? Crypto.decrypt(userAuth.accessToken) + : "", + refreshToken: userAuth?.refreshToken + ? Crypto.decrypt(userAuth.refreshToken) + : "", expirationTimestamp: userAuth?.tokenExpirationTimestamp ?? 0, - subscription: userAuth?.subscription - ? { expiresAt: userAuth.subscription?.expiresAt } + subscription: user?.subscription + ? { expiresAt: user.subscription?.expiresAt } : null, }; @@ -239,14 +245,19 @@ export class HydraApi { this.userAuth.expirationTimestamp ); - userAuthRepository.upsert( - { - id: 1, - accessToken, - tokenExpirationTimestamp, - }, - ["id"] - ); + await db + .get(levelKeys.auth, { valueEncoding: "json" }) + .then((auth) => { + return db.put( + levelKeys.auth, + { + ...auth, + accessToken: Crypto.encrypt(accessToken), + tokenExpirationTimestamp, + }, + { valueEncoding: "json" } + ); + }); } catch (err) { this.handleUnauthorizedError(err); } @@ -276,8 +287,16 @@ export class HydraApi { subscription: null, }; - userAuthRepository.delete({ id: 1 }); - userSubscriptionRepository.delete({ id: 1 }); + db.batch([ + { + type: "del", + key: levelKeys.auth, + }, + { + type: "del", + key: levelKeys.user, + }, + ]); this.sendSignOutEvent(); } diff --git a/src/main/services/index.ts b/src/main/services/index.ts index 5aaf5322..d2034f15 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -1,3 +1,4 @@ +export * from "./crypto"; export * from "./logger"; export * from "./steam"; export * from "./steam-250"; diff --git a/src/main/services/user/get-user-data.ts b/src/main/services/user/get-user-data.ts index 7e924454..e6cf1c71 100644 --- a/src/main/services/user/get-user-data.ts +++ b/src/main/services/user/get-user-data.ts @@ -1,43 +1,30 @@ -import type { ProfileVisibility, UserDetails } from "@types"; +import { User, type ProfileVisibility, type UserDetails } from "@types"; import { HydraApi } from "../hydra-api"; -import { - userAuthRepository, - userSubscriptionRepository, -} from "@main/repository"; import { UserNotLoggedInError } from "@shared"; import { logger } from "../logger"; +import { db } from "@main/level"; +import { levelKeys } from "@main/level/sublevels/keys"; -export const getUserData = () => { +export const getUserData = async () => { return HydraApi.get(`/profile/me`) .then(async (me) => { - userAuthRepository.upsert( - { - id: 1, - displayName: me.displayName, - profileImageUrl: me.profileImageUrl, - backgroundImageUrl: me.backgroundImageUrl, - userId: me.id, - }, - ["id"] + db.get(levelKeys.user, { valueEncoding: "json" }).then( + (user) => { + return db.put( + levelKeys.user, + { + ...user, + id: me.id, + displayName: me.displayName, + profileImageUrl: me.profileImageUrl, + backgroundImageUrl: me.backgroundImageUrl, + subscription: me.subscription, + }, + { valueEncoding: "json" } + ); + } ); - if (me.subscription) { - await userSubscriptionRepository.upsert( - { - id: 1, - subscriptionId: me.subscription?.id || "", - status: me.subscription?.status || "", - planId: me.subscription?.plan.id || "", - planName: me.subscription?.plan.name || "", - expiresAt: me.subscription?.expiresAt || null, - user: { id: 1 }, - }, - ["id"] - ); - } else { - await userSubscriptionRepository.delete({ id: 1 }); - } - return me; }) .catch(async (err) => { @@ -46,15 +33,14 @@ export const getUserData = () => { return null; } logger.error("Failed to get logged user"); - const loggedUser = await userAuthRepository.findOne({ - where: { id: 1 }, - relations: { subscription: true }, + + const loggedUser = await db.get(levelKeys.user, { + valueEncoding: "json", }); if (loggedUser) { return { ...loggedUser, - id: loggedUser.userId, username: "", bio: "", email: null, @@ -64,11 +50,11 @@ export const getUserData = () => { }, subscription: loggedUser.subscription ? { - id: loggedUser.subscription.subscriptionId, + id: loggedUser.subscription.id, status: loggedUser.subscription.status, plan: { - id: loggedUser.subscription.planId, - name: loggedUser.subscription.planName, + id: loggedUser.subscription.plan.id, + name: loggedUser.subscription.plan.name, }, expiresAt: loggedUser.subscription.expiresAt, } diff --git a/src/renderer/src/components/modal/modal.tsx b/src/renderer/src/components/modal/modal.tsx index d8d0554d..af09ef38 100644 --- a/src/renderer/src/components/modal/modal.tsx +++ b/src/renderer/src/components/modal/modal.tsx @@ -52,6 +52,7 @@ export function Modal({ ) ) return false; + const openModals = document.querySelectorAll("[role=dialog]"); return ( diff --git a/src/types/index.ts b/src/types/index.ts index 345893a5..bae42702 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -248,16 +248,6 @@ export interface UserProfileCurrentGame extends Omit { export type ProfileVisibility = "PUBLIC" | "PRIVATE" | "FRIENDS"; -export type SubscriptionStatus = "active" | "pending" | "cancelled"; - -export interface Subscription { - id: string; - status: SubscriptionStatus; - plan: { id: string; name: string }; - expiresAt: string | null; - paymentMethod: "pix" | "paypal"; -} - export interface UserDetails { id: string; username: string; @@ -421,3 +411,4 @@ export * from "./real-debrid.types"; export * from "./ludusavi.types"; export * from "./how-long-to-beat.types"; export * from "./torbox.types"; +export * from "./level.types"; diff --git a/yarn.lock b/yarn.lock index 69ee75d8..4e58584e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3699,6 +3699,18 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" +abstract-level@^2.0.0, abstract-level@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/abstract-level/-/abstract-level-2.0.2.tgz#8d965e731afb42a72f163874410c1687fb2e4bdb" + integrity sha512-pPJixmXk/kTKLB2sSue7o4Uj6TlLD2XfaP2gWZomHVCC6cuUGX/VslQqKG1yZHfXwBb/3lS6oSTMPGzh1P1iig== + dependencies: + buffer "^6.0.3" + is-buffer "^2.0.5" + level-supports "^6.0.0" + level-transcoder "^1.0.1" + maybe-combine-errors "^1.0.0" + module-error "^1.0.1" + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" @@ -4169,6 +4181,13 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" +browser-level@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/browser-level/-/browser-level-2.0.0.tgz#cc63eb1322e67c44489d7fbdda5c30a2db7b59da" + integrity sha512-RuYSCHG/jwFCrK+KWA3dLSUNLKHEgIYhO5ORPjJMjCt7T3e+RzpIDmYKWRHxq2pfKGXjlRuEff7y7RESAAgzew== + dependencies: + abstract-level "^2.0.1" + browserslist@^4.22.2, browserslist@^4.23.1: version "4.24.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4" @@ -4421,6 +4440,16 @@ ci-info@^3.2.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== +classic-level@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/classic-level/-/classic-level-2.0.0.tgz#6fd9ca686bbcd645e35caaf403c3f3a56495d11b" + integrity sha512-ftiMvKgCQK+OppXcvMieDoYlYLYWhScK6yZRFBrrlHQRbm4k6Gr+yDgu/wt3V0k1/jtNbuiXAsRmuAFcD0Tx5Q== + dependencies: + abstract-level "^2.0.0" + module-error "^1.0.1" + napi-macros "^2.2.2" + node-gyp-build "^4.3.0" + classnames@^2.2.1, classnames@^2.2.6, classnames@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" @@ -6459,6 +6488,11 @@ is-boolean-object@^1.2.0: call-bound "^1.0.2" has-tostringtag "^1.0.2" +is-buffer@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -6958,6 +6992,28 @@ lazy-val@^1.0.5: resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.5.tgz#6cf3b9f5bc31cee7ee3e369c0832b7583dcd923d" integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q== +level-supports@^6.0.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/level-supports/-/level-supports-6.2.0.tgz#e78b228973a24acdc5199c5f51e244e70f26c611" + integrity sha512-QNxVXP0IRnBmMsJIh+sb2kwNCYcKciQZJEt+L1hPCHrKNELllXhvrlClVHXBYZVT+a7aTSM6StgNXdAldoab3w== + +level-transcoder@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/level-transcoder/-/level-transcoder-1.0.1.tgz#f8cef5990c4f1283d4c86d949e73631b0bc8ba9c" + integrity sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w== + dependencies: + buffer "^6.0.3" + module-error "^1.0.1" + +level@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/level/-/level-9.0.0.tgz#880aa9d341a5411e36bed77f4fa233f425b492a8" + integrity sha512-n+mVuf63mUEkd8NUx7gwxY+QF5vtkibv6fXTGUgtHWLPDaA5/XZjLcI/Q1nQ8k6OttHT6Ezt+7nSEXsRUfHtOQ== + dependencies: + abstract-level "^2.0.1" + browser-level "^2.0.0" + classic-level "^2.0.0" + levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -7198,6 +7254,11 @@ math-intrinsics@^1.0.0: resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.0.0.tgz#4e04bf87c85aa51e90d078dac2252b4eb5260817" integrity sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA== +maybe-combine-errors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/maybe-combine-errors/-/maybe-combine-errors-1.0.0.tgz#e9592832e61fc47643a92cff3c1f33e27211e5be" + integrity sha512-eefp6IduNPT6fVdwPp+1NgD0PML1NU5P6j1Mj5nz1nidX8/sWY7119WL8vTAHgqfsY74TzW0w1XPgdYEKkGZ5A== + media-query-parser@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/media-query-parser/-/media-query-parser-2.0.2.tgz#ff79e56cee92615a304a1c2fa4f2bd056c0a1d29" @@ -7414,6 +7475,11 @@ modern-ahocorasick@^1.0.0: resolved "https://registry.yarnpkg.com/modern-ahocorasick/-/modern-ahocorasick-1.0.1.tgz#dec373444f51b5458ac05216a8ec376e126dd283" integrity sha512-yoe+JbhTClckZ67b2itRtistFKf8yPYelHLc7e5xAwtNAXxM6wJTUx2C7QeVSJFDzKT7bCIFyBVybPMKvmB9AA== +module-error@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/module-error/-/module-error-1.0.2.tgz#8d1a48897ca883f47a45816d4fb3e3c6ba404d86" + integrity sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -7448,6 +7514,11 @@ napi-build-utils@^1.0.1: resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== +napi-macros@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.2.2.tgz#817fef20c3e0e40a963fbf7b37d1600bd0201044" + integrity sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -7506,6 +7577,11 @@ node-fetch@^3.3.0: fetch-blob "^3.1.4" formdata-polyfill "^4.0.10" +node-gyp-build@^4.3.0: + version "4.8.4" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8" + integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ== + node-gyp@^9.0.0: version "9.4.1" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.4.1.tgz#8a1023e0d6766ecb52764cc3a734b36ff275e185" From 08bcf096411e802919acb607f7eebe56d492a90b Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 15 Jan 2025 17:00:27 +0000 Subject: [PATCH 048/291] feat: adding initial leveldb configuration --- src/main/services/hosters/datanodes.ts | 3 ++- src/types/index.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/services/hosters/datanodes.ts b/src/main/services/hosters/datanodes.ts index d77e7d51..ae144418 100644 --- a/src/main/services/hosters/datanodes.ts +++ b/src/main/services/hosters/datanodes.ts @@ -33,7 +33,8 @@ export class DatanodesApi { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", }, - maxRedirects: 0, validateStatus: (status: number) => status === 302 || status < 400, + maxRedirects: 0, + validateStatus: (status: number) => status === 302 || status < 400, } ); diff --git a/src/types/index.ts b/src/types/index.ts index bae42702..dd631ccb 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,5 +1,6 @@ import type { Cracker, DownloadSourceStatus, Downloader } from "@shared"; import type { SteamAppDetails } from "./steam.types"; +import type { Subscription } from "./level.types"; export type GameStatus = | "active" From c59b039eb4cb5a0eae558cb77cc8cedeb57ed441 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 15 Jan 2025 17:02:40 +0000 Subject: [PATCH 049/291] fix: removing unused navigate --- src/renderer/src/pages/achievements/achievements.tsx | 2 +- .../src/pages/profile/profile-content/profile-content.tsx | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/renderer/src/pages/achievements/achievements.tsx b/src/renderer/src/pages/achievements/achievements.tsx index 605300ef..f467cf89 100644 --- a/src/renderer/src/pages/achievements/achievements.tsx +++ b/src/renderer/src/pages/achievements/achievements.tsx @@ -44,7 +44,7 @@ export default function Achievements() { .getComparedUnlockedAchievements(objectId, shop as GameShop, userId) .then(setComparedAchievements); } - }, [objectId, shop, userId]); + }, [objectId, shop, userDetails?.id, userId]); const otherUserId = userDetails?.id === userId ? null : userId; diff --git a/src/renderer/src/pages/profile/profile-content/profile-content.tsx b/src/renderer/src/pages/profile/profile-content/profile-content.tsx index 951eb41b..71788a32 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-content.tsx +++ b/src/renderer/src/pages/profile/profile-content/profile-content.tsx @@ -7,7 +7,6 @@ import { SPACING_UNIT } from "@renderer/theme.css"; import * as styles from "./profile-content.css"; import { TelescopeIcon } from "@primer/octicons-react"; import { useTranslation } from "react-i18next"; -import { useNavigate } from "react-router-dom"; import { LockedProfile } from "./locked-profile"; import { ReportProfile } from "../report-profile/report-profile"; import { FriendsBox } from "./friends-box"; @@ -66,8 +65,6 @@ export function ProfileContent() { const { numberFormatter } = useFormat(); - const navigate = useNavigate(); - const usersAreFriends = useMemo(() => { return userProfile?.relation?.status === "ACCEPTED"; }, [userProfile]); @@ -148,7 +145,6 @@ export function ProfileContent() { userStats, numberFormatter, t, - navigate, statsIndex, ]); From 8b47082047b6e89adb59a39cafadcc45ea525078 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 15 Jan 2025 17:08:22 +0000 Subject: [PATCH 050/291] fix: removing unused navigate --- src/main/level/index.ts | 3 +++ src/main/level/level.ts | 4 ++++ src/main/level/sublevels/games.ts | 7 +++++++ src/main/level/sublevels/index.ts | 1 + src/main/level/sublevels/keys.ts | 8 ++++++++ src/main/services/crypto.ts | 28 ++++++++++++++++++++++++++++ src/types/level.types.ts | 23 +++++++++++++++++++++++ 7 files changed, 74 insertions(+) create mode 100644 src/main/level/index.ts create mode 100644 src/main/level/level.ts create mode 100644 src/main/level/sublevels/games.ts create mode 100644 src/main/level/sublevels/index.ts create mode 100644 src/main/level/sublevels/keys.ts create mode 100644 src/main/services/crypto.ts create mode 100644 src/types/level.types.ts diff --git a/src/main/level/index.ts b/src/main/level/index.ts new file mode 100644 index 00000000..90a34be3 --- /dev/null +++ b/src/main/level/index.ts @@ -0,0 +1,3 @@ +export { db } from "./level"; + +export * from "./sublevels"; diff --git a/src/main/level/level.ts b/src/main/level/level.ts new file mode 100644 index 00000000..382c61a5 --- /dev/null +++ b/src/main/level/level.ts @@ -0,0 +1,4 @@ +import { levelDatabasePath } from "@main/constants"; +import { Level } from "level"; + +export const db = new Level(levelDatabasePath, { valueEncoding: "json" }); diff --git a/src/main/level/sublevels/games.ts b/src/main/level/sublevels/games.ts new file mode 100644 index 00000000..bc0cad30 --- /dev/null +++ b/src/main/level/sublevels/games.ts @@ -0,0 +1,7 @@ +import { Game } from "@types"; +import { db } from "../level"; +import { levelKeys } from "./keys"; + +export const gamesSublevel = db.sublevel(levelKeys.games, { + valueEncoding: "json", +}); diff --git a/src/main/level/sublevels/index.ts b/src/main/level/sublevels/index.ts new file mode 100644 index 00000000..9d316e1a --- /dev/null +++ b/src/main/level/sublevels/index.ts @@ -0,0 +1 @@ +export * from "./games"; diff --git a/src/main/level/sublevels/keys.ts b/src/main/level/sublevels/keys.ts new file mode 100644 index 00000000..6bb54c4a --- /dev/null +++ b/src/main/level/sublevels/keys.ts @@ -0,0 +1,8 @@ +import type { GameShop } from "@types"; + +export const levelKeys = { + games: "games", + game: (shop: GameShop, objectId: string) => `${shop}:${objectId}`, + user: "user", + auth: "auth", +}; diff --git a/src/main/services/crypto.ts b/src/main/services/crypto.ts new file mode 100644 index 00000000..63a50668 --- /dev/null +++ b/src/main/services/crypto.ts @@ -0,0 +1,28 @@ +import { safeStorage } from "electron"; +import { logger } from "./logger"; + +export class Crypto { + public static encrypt(str: string) { + if (safeStorage.isEncryptionAvailable()) { + return safeStorage.encryptString(str).toString("base64"); + } else { + logger.warn( + "Encrypt method returned raw string because encryption is not available" + ); + + return str; + } + } + + public static decrypt(b64: string) { + if (safeStorage.isEncryptionAvailable()) { + return safeStorage.decryptString(Buffer.from(b64, "base64")); + } else { + logger.warn( + "Decrypt method returned raw string because encryption is not available" + ); + + return b64; + } + } +} diff --git a/src/types/level.types.ts b/src/types/level.types.ts new file mode 100644 index 00000000..490ab060 --- /dev/null +++ b/src/types/level.types.ts @@ -0,0 +1,23 @@ +export type SubscriptionStatus = "active" | "pending" | "cancelled"; + +export interface Subscription { + id: string; + status: SubscriptionStatus; + plan: { id: string; name: string }; + expiresAt: string | null; + paymentMethod: "pix" | "paypal"; +} + +export interface Auth { + accessToken: string; + refreshToken: string; + tokenExpirationTimestamp: number; +} + +export interface User { + id: string; + displayName: string; + profileImageUrl: string | null; + backgroundImageUrl: string | null; + subscription: Subscription | null; +} From 2c881a61002390a48438f6118b2ce4197c852072 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 15 Jan 2025 17:15:57 +0000 Subject: [PATCH 051/291] fix: fixing duplicate export --- src/main/entity/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/entity/index.ts b/src/main/entity/index.ts index ab0ebff9..06b543d4 100644 --- a/src/main/entity/index.ts +++ b/src/main/entity/index.ts @@ -1,6 +1,5 @@ export * from "./game.entity"; export * from "./user-preferences.entity"; export * from "./game-shop-cache.entity"; -export * from "./game.entity"; export * from "./game-achievements.entity"; export * from "./download-queue.entity"; From af4fcb8f064a5f8bb6db5180ebe23c506e80e87a Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:49:11 -0300 Subject: [PATCH 052/291] feat: manage account buttons --- src/locales/en/translation.json | 13 +- src/locales/pt-BR/translation.json | 13 +- src/main/events/index.ts | 1 + src/main/events/misc/open-manage-account.ts | 25 ++ src/main/services/hydra-api.ts | 14 +- src/preload/index.ts | 3 + src/renderer/src/declaration.d.ts | 2 + ...privacy.css.ts => settings-account.css.ts} | 0 .../src/pages/settings/settings-account.tsx | 217 ++++++++++++++++++ .../src/pages/settings/settings-privacy.tsx | 139 ----------- src/renderer/src/pages/settings/settings.tsx | 6 +- src/types/index.ts | 2 + 12 files changed, 286 insertions(+), 149 deletions(-) create mode 100644 src/main/events/misc/open-manage-account.ts rename src/renderer/src/pages/settings/{settings-privacy.css.ts => settings-account.css.ts} (100%) create mode 100644 src/renderer/src/pages/settings/settings-account.tsx delete mode 100644 src/renderer/src/pages/settings/settings-privacy.tsx diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 4e3dcb37..e55954bf 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -280,7 +280,18 @@ "launch_minimized": "Launch Hydra minimized", "disable_nsfw_alert": "Disable NSFW alert", "seed_after_download_complete": "Seed after download complete", - "show_hidden_achievement_description": "Show hidden achievements description before unlocking them" + "show_hidden_achievement_description": "Show hidden achievements description before unlocking them", + "account": "Account", + "no_users_blocked": "You have no blocked users", + "subscription": "Hydra Cloud subscription", + "subscription_active_until": "Your Hydra Cloud is active until {{date}}", + "subscription_not_active": "You don't have an active Hydra Cloud subscription", + "manage_account": "Manage account", + "manage_subscription": "Manage subscription", + "update_email": "Update email", + "update_password": "Update password", + "current_email": "Current email:", + "no_associated_email": "You don't have an associated email yet" }, "notifications": { "download_complete": "Download complete", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 2a80084f..24ab4653 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -268,7 +268,18 @@ "launch_minimized": "Iniciar o Hydra minimizado", "disable_nsfw_alert": "Desativar alerta de conteúdo inapropriado", "seed_after_download_complete": "Semear após a conclusão do download", - "show_hidden_achievement_description": "Mostrar descrição de conquistas ocultas antes de debloqueá-las" + "show_hidden_achievement_description": "Mostrar descrição de conquistas ocultas antes de debloqueá-las", + "account": "Conta", + "no_users_blocked": "Você não bloqueou nenhum usuário", + "subscription": "Assinatura Hydra Cloud", + "subscription_active_until": "Seu Hydra Cloud ficará ativo até {{date}}", + "subscription_not_active": "Você não possui uma assinatura Hydra Cloud ativa", + "manage_account": "Gerenciar conta", + "manage_subscription": "Gerenciar assinatura", + "update_email": "Atualizar email", + "update_password": "Atualizar senha", + "current_email": "Email atual:", + "no_associated_email": "Você ainda não associou nenhum email a sua conta" }, "notifications": { "download_complete": "Download concluído", diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 25882c3f..f0f882ca 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -30,6 +30,7 @@ import "./library/remove-game-from-library"; import "./library/select-game-wine-prefix"; import "./library/reset-game-achievements"; import "./misc/open-checkout"; +import "./misc/open-manage-account"; import "./misc/open-external"; import "./misc/show-open-dialog"; import "./misc/get-features"; diff --git a/src/main/events/misc/open-manage-account.ts b/src/main/events/misc/open-manage-account.ts new file mode 100644 index 00000000..2946b04b --- /dev/null +++ b/src/main/events/misc/open-manage-account.ts @@ -0,0 +1,25 @@ +import { shell } from "electron"; +import { registerEvent } from "../register-event"; +import { HydraApi, logger } from "@main/services"; +import { ManageAccountPage } from "@types"; + +const openManageAccount = async ( + _event: Electron.IpcMainInvokeEvent, + page: ManageAccountPage +) => { + try { + const { accessToken } = await HydraApi.refreshToken(); + + const params = new URLSearchParams({ + token: accessToken, + }); + + shell.openExternal( + `${import.meta.env.MAIN_VITE_AUTH_URL}/${page}?${params.toString()}` + ); + } catch (err) { + logger.error("Failed to open manage account", err); + } +}; + +registerEvent("openManageAccount", openManageAccount); diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 63dd9b16..4ab20e80 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -215,16 +215,20 @@ export class HydraApi { } } + public static async refreshToken() { + return this.instance + .post<{ accessToken: string; expiresIn: number }>(`/auth/refresh`, { + refreshToken: this.userAuth.refreshToken, + }) + .then((response) => response.data); + } + private static async revalidateAccessTokenIfExpired() { const now = new Date(); if (this.userAuth.expirationTimestamp < now.getTime()) { try { - const response = await this.instance.post(`/auth/refresh`, { - refreshToken: this.userAuth.refreshToken, - }); - - const { accessToken, expiresIn } = response.data; + const { accessToken, expiresIn } = await this.refreshToken(); const tokenExpirationTimestamp = now.getTime() + diff --git a/src/preload/index.ts b/src/preload/index.ts index 316397d2..24e6cf39 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -14,6 +14,7 @@ import type { CatalogueSearchPayload, SeedingStatus, GameAchievement, + ManageAccountPage, } from "@types"; import type { CatalogueCategory } from "@shared"; import type { AxiosProgressEvent } from "axios"; @@ -226,6 +227,8 @@ contextBridge.exposeInMainWorld("electron", { isPortableVersion: () => ipcRenderer.invoke("isPortableVersion"), openExternal: (src: string) => ipcRenderer.invoke("openExternal", src), openCheckout: () => ipcRenderer.invoke("openCheckout"), + openManageAccount: (page: ManageAccountPage) => + ipcRenderer.invoke("openManageAccount", page), showOpenDialog: (options: Electron.OpenDialogOptions) => ipcRenderer.invoke("showOpenDialog", options), showItemInFolder: (path: string) => diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 88f3297f..33fcb7da 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -29,6 +29,7 @@ import type { UserAchievement, ComparedAchievements, CatalogueSearchPayload, + ManageAccountPage, } from "@types"; import type { AxiosProgressEvent } from "axios"; import type disk from "diskusage"; @@ -187,6 +188,7 @@ declare global { /* Misc */ openExternal: (src: string) => Promise; openCheckout: () => Promise; + openManageAccount: (page: ManageAccountPage) => Promise; getVersion: () => Promise; isStaging: () => Promise; ping: () => string; diff --git a/src/renderer/src/pages/settings/settings-privacy.css.ts b/src/renderer/src/pages/settings/settings-account.css.ts similarity index 100% rename from src/renderer/src/pages/settings/settings-privacy.css.ts rename to src/renderer/src/pages/settings/settings-account.css.ts diff --git a/src/renderer/src/pages/settings/settings-account.tsx b/src/renderer/src/pages/settings/settings-account.tsx new file mode 100644 index 00000000..16ed40e4 --- /dev/null +++ b/src/renderer/src/pages/settings/settings-account.tsx @@ -0,0 +1,217 @@ +import { Button, SelectField } from "@renderer/components"; +import { SPACING_UNIT, vars } from "@renderer/theme.css"; +import { Controller, useForm } from "react-hook-form"; +import { useTranslation } from "react-i18next"; + +import * as styles from "./settings-account.css"; +import { useDate, useToast, useUserDetails } from "@renderer/hooks"; +import { useCallback, useContext, useEffect, useState } from "react"; +import { + CloudIcon, + KeyIcon, + MailIcon, + XCircleFillIcon, +} from "@primer/octicons-react"; +import { settingsContext } from "@renderer/context"; + +interface FormValues { + profileVisibility: "PUBLIC" | "FRIENDS" | "PRIVATE"; +} + +export function SettingsAccount() { + const { t } = useTranslation("settings"); + + const [isUnblocking, setIsUnblocking] = useState(false); + + const { showSuccessToast } = useToast(); + + const { blockedUsers, fetchBlockedUsers } = useContext(settingsContext); + + const { formatDate } = useDate(); + + const { + control, + formState: { isSubmitting }, + setValue, + handleSubmit, + } = useForm(); + + const { patchUser, userDetails } = useUserDetails(); + + const { unblockUser } = useUserDetails(); + + useEffect(() => { + if (userDetails?.profileVisibility) { + setValue("profileVisibility", userDetails.profileVisibility); + } + }, [userDetails, setValue]); + + const visibilityOptions = [ + { value: "PUBLIC", label: t("public") }, + { value: "FRIENDS", label: t("friends_only") }, + { value: "PRIVATE", label: t("private") }, + ]; + + const onSubmit = async (values: FormValues) => { + await patchUser(values); + showSuccessToast(t("changes_saved")); + }; + + const handleUnblockClick = useCallback( + (id: string) => { + setIsUnblocking(true); + + unblockUser(id) + .then(() => { + fetchBlockedUsers(); + showSuccessToast(t("user_unblocked")); + }) + .finally(() => { + setIsUnblocking(false); + }); + }, + [unblockUser, fetchBlockedUsers, t, showSuccessToast] + ); + + return ( +
+ { + const handleChange = ( + event: React.ChangeEvent + ) => { + field.onChange(event); + handleSubmit(onSubmit)(); + }; + + return ( + <> + ({ + key: visiblity.value, + value: visiblity.value, + label: visiblity.label, + }))} + disabled={isSubmitting} + /> + + {t("profile_visibility_description")} + + ); + }} + /> + +

+ {t("manage_account")} +

+ + {userDetails?.email ? ( +
+

{t("current_email")}

+

{userDetails.email}

+
+ ) : ( +

{t("no_associated_email")}

+ )} + +
+ + + +
+ +

+ {t("subscription")} +

+ {userDetails?.subscription?.expiresAt ? ( +

+ {t("subscription_active_until", { + date: formatDate(userDetails?.subscription?.expiresAt), + })} +

+ ) : ( +

{t("subscription_not_active")}

+ )} + +
+ +
+ +

+ {t("blocked_users")} +

+ +
    + {blockedUsers.length > 0 ? ( + blockedUsers.map((user) => { + return ( +
  • +
    + {user.displayName} + {user.displayName} +
    + + +
  • + ); + }) + ) : ( + {t("no_users_blocked")} + )} +
+ + ); +} diff --git a/src/renderer/src/pages/settings/settings-privacy.tsx b/src/renderer/src/pages/settings/settings-privacy.tsx deleted file mode 100644 index b93d1d07..00000000 --- a/src/renderer/src/pages/settings/settings-privacy.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import { SelectField } from "@renderer/components"; -import { SPACING_UNIT } from "@renderer/theme.css"; -import { Controller, useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; - -import * as styles from "./settings-privacy.css"; -import { useToast, useUserDetails } from "@renderer/hooks"; -import { useCallback, useContext, useEffect, useState } from "react"; -import { XCircleFillIcon } from "@primer/octicons-react"; -import { settingsContext } from "@renderer/context"; - -interface FormValues { - profileVisibility: "PUBLIC" | "FRIENDS" | "PRIVATE"; -} - -export function SettingsPrivacy() { - const { t } = useTranslation("settings"); - - const [isUnblocking, setIsUnblocking] = useState(false); - - const { showSuccessToast } = useToast(); - - const { blockedUsers, fetchBlockedUsers } = useContext(settingsContext); - - const { - control, - formState: { isSubmitting }, - setValue, - handleSubmit, - } = useForm(); - - const { patchUser, userDetails } = useUserDetails(); - - const { unblockUser } = useUserDetails(); - - useEffect(() => { - if (userDetails?.profileVisibility) { - setValue("profileVisibility", userDetails.profileVisibility); - } - }, [userDetails, setValue]); - - const visibilityOptions = [ - { value: "PUBLIC", label: t("public") }, - { value: "FRIENDS", label: t("friends_only") }, - { value: "PRIVATE", label: t("private") }, - ]; - - const onSubmit = async (values: FormValues) => { - await patchUser(values); - showSuccessToast(t("changes_saved")); - }; - - const handleUnblockClick = useCallback( - (id: string) => { - setIsUnblocking(true); - - unblockUser(id) - .then(() => { - fetchBlockedUsers(); - showSuccessToast(t("user_unblocked")); - }) - .finally(() => { - setIsUnblocking(false); - }); - }, - [unblockUser, fetchBlockedUsers, t, showSuccessToast] - ); - - return ( -
- { - const handleChange = ( - event: React.ChangeEvent - ) => { - field.onChange(event); - handleSubmit(onSubmit)(); - }; - - return ( - <> - ({ - key: visiblity.value, - value: visiblity.value, - label: visiblity.label, - }))} - disabled={isSubmitting} - /> - - {t("profile_visibility_description")} - - ); - }} - /> - -

- {t("blocked_users")} -

- -
    - {blockedUsers.map((user) => { - return ( -
  • -
    - {user.displayName} - {user.displayName} -
    - - -
  • - ); - })} -
- - ); -} diff --git a/src/renderer/src/pages/settings/settings.tsx b/src/renderer/src/pages/settings/settings.tsx index dffdfbae..5fba6c5d 100644 --- a/src/renderer/src/pages/settings/settings.tsx +++ b/src/renderer/src/pages/settings/settings.tsx @@ -11,7 +11,7 @@ import { SettingsContextConsumer, SettingsContextProvider, } from "@renderer/context"; -import { SettingsPrivacy } from "./settings-privacy"; +import { SettingsAccount } from "./settings-account"; import { useUserDetails } from "@renderer/hooks"; import { useMemo } from "react"; @@ -28,7 +28,7 @@ export default function Settings() { "Real-Debrid", ]; - if (userDetails) return [...categories, t("privacy")]; + if (userDetails) return [...categories, t("account")]; return categories; }, [userDetails, t]); @@ -53,7 +53,7 @@ export default function Settings() { return ; } - return ; + return ; }; return ( diff --git a/src/types/index.ts b/src/types/index.ts index 345893a5..66c458b5 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -416,6 +416,8 @@ export interface CatalogueSearchPayload { developers: string[]; } +export type ManageAccountPage = "update-email" | "update-password"; + export * from "./steam.types"; export * from "./real-debrid.types"; export * from "./ludusavi.types"; From c4378c0ffc44af29a78d123e56559c34b3c3dd8c Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:26:44 -0300 Subject: [PATCH 053/291] feat: update user details on settings account tab --- src/main/services/hosters/datanodes.ts | 3 ++- .../src/pages/settings/settings-account.tsx | 20 +++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/main/services/hosters/datanodes.ts b/src/main/services/hosters/datanodes.ts index d77e7d51..ae144418 100644 --- a/src/main/services/hosters/datanodes.ts +++ b/src/main/services/hosters/datanodes.ts @@ -33,7 +33,8 @@ export class DatanodesApi { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", }, - maxRedirects: 0, validateStatus: (status: number) => status === 302 || status < 400, + maxRedirects: 0, + validateStatus: (status: number) => status === 302 || status < 400, } ); diff --git a/src/renderer/src/pages/settings/settings-account.tsx b/src/renderer/src/pages/settings/settings-account.tsx index 16ed40e4..14fc3564 100644 --- a/src/renderer/src/pages/settings/settings-account.tsx +++ b/src/renderer/src/pages/settings/settings-account.tsx @@ -1,5 +1,5 @@ import { Button, SelectField } from "@renderer/components"; -import { SPACING_UNIT, vars } from "@renderer/theme.css"; +import { SPACING_UNIT } from "@renderer/theme.css"; import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; @@ -36,9 +36,13 @@ export function SettingsAccount() { handleSubmit, } = useForm(); - const { patchUser, userDetails } = useUserDetails(); - - const { unblockUser } = useUserDetails(); + const { + userDetails, + patchUser, + fetchUserDetails, + updateUserDetails, + unblockUser, + } = useUserDetails(); useEffect(() => { if (userDetails?.profileVisibility) { @@ -46,6 +50,14 @@ export function SettingsAccount() { } }, [userDetails, setValue]); + useEffect(() => { + fetchUserDetails().then((response) => { + if (response) { + updateUserDetails(response); + } + }); + }, []); + const visibilityOptions = [ { value: "PUBLIC", label: t("public") }, { value: "FRIENDS", label: t("friends_only") }, From ffd3e37b484994a80de531ce499cb927ba68a5ec Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:57:25 -0300 Subject: [PATCH 054/291] feat: refactor --- src/main/services/hydra-api.ts | 56 +++++++++++++++++----------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 4ab20e80..16bbc21f 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -216,41 +216,41 @@ export class HydraApi { } public static async refreshToken() { - return this.instance + const { accessToken, expiresIn } = await this.instance .post<{ accessToken: string; expiresIn: number }>(`/auth/refresh`, { refreshToken: this.userAuth.refreshToken, }) .then((response) => response.data); + + const tokenExpirationTimestamp = + Date.now() + + this.secondsToMilliseconds(expiresIn) - + this.EXPIRATION_OFFSET_IN_MS; + + this.userAuth.authToken = accessToken; + this.userAuth.expirationTimestamp = tokenExpirationTimestamp; + + logger.log( + "Token refreshed. New expiration:", + this.userAuth.expirationTimestamp + ); + + userAuthRepository.upsert( + { + id: 1, + accessToken, + tokenExpirationTimestamp, + }, + ["id"] + ); + + return { accessToken, expiresIn }; } private static async revalidateAccessTokenIfExpired() { - const now = new Date(); - - if (this.userAuth.expirationTimestamp < now.getTime()) { + if (this.userAuth.expirationTimestamp < Date.now()) { try { - const { accessToken, expiresIn } = await this.refreshToken(); - - const tokenExpirationTimestamp = - now.getTime() + - this.secondsToMilliseconds(expiresIn) - - this.EXPIRATION_OFFSET_IN_MS; - - this.userAuth.authToken = accessToken; - this.userAuth.expirationTimestamp = tokenExpirationTimestamp; - - logger.log( - "Token refreshed. New expiration:", - this.userAuth.expirationTimestamp - ); - - userAuthRepository.upsert( - { - id: 1, - accessToken, - tokenExpirationTimestamp, - }, - ["id"] - ); + await this.refreshToken(); } catch (err) { this.handleUnauthorizedError(err); } @@ -265,7 +265,7 @@ export class HydraApi { }; } - private static handleUnauthorizedError = (err) => { + private static readonly handleUnauthorizedError = (err) => { if (err instanceof AxiosError && err.response?.status === 401) { logger.error( "401 - Current credentials:", From 56fabb288110e7809c3c0be1f75451006ea7fd36 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 15 Jan 2025 17:08:07 -0300 Subject: [PATCH 055/291] fix: hook dependencies --- src/renderer/src/pages/settings/settings-account.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/pages/settings/settings-account.tsx b/src/renderer/src/pages/settings/settings-account.tsx index 14fc3564..89f5bde1 100644 --- a/src/renderer/src/pages/settings/settings-account.tsx +++ b/src/renderer/src/pages/settings/settings-account.tsx @@ -56,7 +56,7 @@ export function SettingsAccount() { updateUserDetails(response); } }); - }, []); + }, [fetchUserDetails, updateUserDetails]); const visibilityOptions = [ { value: "PUBLIC", label: t("public") }, From 15f721ac3935d1bfc0ae7c32b8f88e90fbafbcfa Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 15 Jan 2025 17:11:02 -0300 Subject: [PATCH 056/291] feat: use Avatar component and remove non null assertion --- src/renderer/src/pages/settings/settings-account.css.ts | 7 ------- src/renderer/src/pages/settings/settings-account.tsx | 9 +++++---- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/renderer/src/pages/settings/settings-account.css.ts b/src/renderer/src/pages/settings/settings-account.css.ts index 2aec8cd0..44ec0d14 100644 --- a/src/renderer/src/pages/settings/settings-account.css.ts +++ b/src/renderer/src/pages/settings/settings-account.css.ts @@ -8,13 +8,6 @@ export const form = style({ gap: `${SPACING_UNIT}px`, }); -export const blockedUserAvatar = style({ - width: "32px", - height: "32px", - borderRadius: "4px", - filter: "grayscale(100%)", -}); - export const blockedUser = style({ display: "flex", minWidth: "240px", diff --git a/src/renderer/src/pages/settings/settings-account.tsx b/src/renderer/src/pages/settings/settings-account.tsx index 89f5bde1..0aedb4b8 100644 --- a/src/renderer/src/pages/settings/settings-account.tsx +++ b/src/renderer/src/pages/settings/settings-account.tsx @@ -1,4 +1,4 @@ -import { Button, SelectField } from "@renderer/components"; +import { Avatar, Button, SelectField } from "@renderer/components"; import { SPACING_UNIT } from "@renderer/theme.css"; import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; @@ -201,10 +201,11 @@ export function SettingsAccount() { alignItems: "center", }} > - {user.displayName} {user.displayName}
From 9941460c60afab8a9063c2cfcf55ce87cadd006d Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 15 Jan 2025 17:13:36 -0300 Subject: [PATCH 057/291] feat: code review --- src/renderer/src/pages/settings/settings-account.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/pages/settings/settings-account.tsx b/src/renderer/src/pages/settings/settings-account.tsx index 0aedb4b8..6ba4cb7a 100644 --- a/src/renderer/src/pages/settings/settings-account.tsx +++ b/src/renderer/src/pages/settings/settings-account.tsx @@ -163,7 +163,7 @@ export function SettingsAccount() { {userDetails?.subscription?.expiresAt ? (

{t("subscription_active_until", { - date: formatDate(userDetails?.subscription?.expiresAt), + date: formatDate(userDetails.subscription.expiresAt), })}

) : ( From a23106b0b1d62b257b7aef1a9dd365d7ef92a967 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Thu, 16 Jan 2025 02:30:09 +0000 Subject: [PATCH 058/291] feat: migrating achievements to level --- src/main/data-source.ts | 16 +- src/main/entity/game-achievements.entity.ts | 19 -- src/main/entity/game-shop-cache.entity.ts | 35 ---- src/main/entity/index.ts | 2 - src/main/events/auth/get-session-hash.ts | 2 +- src/main/events/auth/sign-out.ts | 2 +- .../events/catalogue/get-game-shop-details.ts | 34 ++-- .../events/library/reset-game-achievements.ts | 24 ++- src/main/events/misc/open-checkout.ts | 2 +- .../events/user/get-unlocked-achievements.ts | 20 +-- src/main/events/user/get-user-friends.ts | 2 +- src/main/level/sublevels/game-achievements.ts | 11 ++ src/main/level/sublevels/game-shop-cache.ts | 11 ++ src/main/level/sublevels/games.ts | 3 +- src/main/level/sublevels/index.ts | 4 + src/main/level/sublevels/keys.ts | 4 + src/main/repository.ts | 13 +- .../achievements/get-game-achievement-data.ts | 48 ++--- .../achievements/merge-achievements.ts | 66 +++---- src/main/services/hydra-api.ts | 2 +- src/main/services/user/get-user-data.ts | 2 +- .../src/components/sidebar/sidebar.tsx | 11 +- src/renderer/src/declaration.d.ts | 6 +- src/renderer/src/features/library-slice.ts | 4 +- .../pages/achievements/achievement-list.tsx | 9 +- .../src/pages/downloads/download-group.tsx | 10 +- .../src/pages/downloads/downloads.tsx | 6 +- .../pages/game-details/sidebar/sidebar.tsx | 4 +- src/types/download.types.ts | 167 ++++++++++++++++++ src/types/game.types.ts | 59 +++++++ src/types/index.ts | 115 +----------- src/types/level.types.ts | 7 + src/types/real-debrid.types.ts | 66 ------- src/types/torbox.types.ts | 77 -------- 34 files changed, 388 insertions(+), 475 deletions(-) delete mode 100644 src/main/entity/game-achievements.entity.ts delete mode 100644 src/main/entity/game-shop-cache.entity.ts create mode 100644 src/main/level/sublevels/game-achievements.ts create mode 100644 src/main/level/sublevels/game-shop-cache.ts create mode 100644 src/types/download.types.ts create mode 100644 src/types/game.types.ts delete mode 100644 src/types/real-debrid.types.ts delete mode 100644 src/types/torbox.types.ts diff --git a/src/main/data-source.ts b/src/main/data-source.ts index 05fdb04d..7414a758 100644 --- a/src/main/data-source.ts +++ b/src/main/data-source.ts @@ -1,23 +1,11 @@ import { DataSource } from "typeorm"; -import { - DownloadQueue, - Game, - GameShopCache, - UserPreferences, - GameAchievement, -} from "@main/entity"; +import { DownloadQueue, Game, UserPreferences } from "@main/entity"; import { databasePath } from "./constants"; export const dataSource = new DataSource({ type: "better-sqlite3", - entities: [ - Game, - UserPreferences, - GameShopCache, - DownloadQueue, - GameAchievement, - ], + entities: [Game, UserPreferences, DownloadQueue], synchronize: false, database: databasePath, }); diff --git a/src/main/entity/game-achievements.entity.ts b/src/main/entity/game-achievements.entity.ts deleted file mode 100644 index 0cb15f6e..00000000 --- a/src/main/entity/game-achievements.entity.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; - -@Entity("game_achievement") -export class GameAchievement { - @PrimaryGeneratedColumn() - id: number; - - @Column("text") - objectId: string; - - @Column("text") - shop: string; - - @Column("text", { nullable: true }) - unlockedAchievements: string | null; - - @Column("text", { nullable: true }) - achievements: string | null; -} diff --git a/src/main/entity/game-shop-cache.entity.ts b/src/main/entity/game-shop-cache.entity.ts deleted file mode 100644 index 3382da1c..00000000 --- a/src/main/entity/game-shop-cache.entity.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { - Entity, - PrimaryColumn, - Column, - CreateDateColumn, - UpdateDateColumn, -} from "typeorm"; -import type { GameShop } from "@types"; - -@Entity("game_shop_cache") -export class GameShopCache { - @PrimaryColumn("text", { unique: true }) - objectID: string; - - @Column("text") - shop: GameShop; - - @Column("text", { nullable: true }) - serializedData: string; - - /** - * @deprecated Use IndexedDB's `howLongToBeatEntries` instead - */ - @Column("text", { nullable: true }) - howLongToBeatSerializedData: string; - - @Column("text", { nullable: true }) - language: string; - - @CreateDateColumn() - createdAt: Date; - - @UpdateDateColumn() - updatedAt: Date; -} diff --git a/src/main/entity/index.ts b/src/main/entity/index.ts index 06b543d4..f35f643d 100644 --- a/src/main/entity/index.ts +++ b/src/main/entity/index.ts @@ -1,5 +1,3 @@ export * from "./game.entity"; export * from "./user-preferences.entity"; -export * from "./game-shop-cache.entity"; -export * from "./game-achievements.entity"; export * from "./download-queue.entity"; diff --git a/src/main/events/auth/get-session-hash.ts b/src/main/events/auth/get-session-hash.ts index 293fb62e..5848cbd7 100644 --- a/src/main/events/auth/get-session-hash.ts +++ b/src/main/events/auth/get-session-hash.ts @@ -3,7 +3,7 @@ import jwt from "jsonwebtoken"; import { registerEvent } from "../register-event"; import { db } from "@main/level"; import type { Auth } from "@types"; -import { levelKeys } from "@main/level/sublevels/keys"; +import { levelKeys } from "@main/level"; import { Crypto } from "@main/services"; const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => { diff --git a/src/main/events/auth/sign-out.ts b/src/main/events/auth/sign-out.ts index 1fb3a054..866d1ec0 100644 --- a/src/main/events/auth/sign-out.ts +++ b/src/main/events/auth/sign-out.ts @@ -4,7 +4,7 @@ import { dataSource } from "@main/data-source"; import { DownloadQueue, Game } from "@main/entity"; import { PythonRPC } from "@main/services/python-rpc"; import { db } from "@main/level"; -import { levelKeys } from "@main/level/sublevels/keys"; +import { levelKeys } from "@main/level"; const signOut = async (_event: Electron.IpcMainInvokeEvent) => { const databaseOperations = dataSource diff --git a/src/main/events/catalogue/get-game-shop-details.ts b/src/main/events/catalogue/get-game-shop-details.ts index 08366abc..39f8425b 100644 --- a/src/main/events/catalogue/get-game-shop-details.ts +++ b/src/main/events/catalogue/get-game-shop-details.ts @@ -1,10 +1,10 @@ -import { gameShopCacheRepository } from "@main/repository"; -import { getSteamAppDetails } from "@main/services"; +import { getSteamAppDetails, logger } from "@main/services"; -import type { ShopDetails, GameShop, SteamAppDetails } from "@types"; +import type { ShopDetails, GameShop } from "@types"; import { registerEvent } from "../register-event"; import { steamGamesWorker } from "@main/workers"; +import { gamesShopCacheSublevel, levelKeys } from "@main/level"; const getLocalizedSteamAppDetails = async ( objectId: string, @@ -39,35 +39,27 @@ const getGameShopDetails = async ( language: string ): Promise => { if (shop === "steam") { - const cachedData = await gameShopCacheRepository.findOne({ - where: { objectID: objectId, language }, - }); + const cachedData = await gamesShopCacheSublevel.get( + levelKeys.gameShopCacheItem(shop, objectId, language) + ); const appDetails = getLocalizedSteamAppDetails(objectId, language).then( (result) => { if (result) { - gameShopCacheRepository.upsert( - { - objectID: objectId, - shop: "steam", - language, - serializedData: JSON.stringify(result), - }, - ["objectID"] - ); + gamesShopCacheSublevel + .put(levelKeys.gameShopCacheItem(shop, objectId, language), result) + .catch((err) => { + logger.error("Could not cache game details", err); + }); } return result; } ); - const cachedGame = cachedData?.serializedData - ? (JSON.parse(cachedData?.serializedData) as SteamAppDetails) - : null; - - if (cachedGame) { + if (cachedData) { return { - ...cachedGame, + ...cachedData, objectId, } as ShopDetails; } diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts index 8d52a3a6..0ea26adf 100644 --- a/src/main/events/library/reset-game-achievements.ts +++ b/src/main/events/library/reset-game-achievements.ts @@ -1,9 +1,10 @@ -import { gameAchievementRepository, gameRepository } from "@main/repository"; +import { gameRepository } from "@main/repository"; import { registerEvent } from "../register-event"; import { findAchievementFiles } from "@main/services/achievements/find-achivement-files"; import fs from "fs"; import { achievementsLogger, HydraApi, WindowManager } from "@main/services"; import { getUnlockedAchievements } from "../user/get-unlocked-achievements"; +import { gameAchievementsSublevel, levelKeys } from "@main/level"; const resetGameAchievements = async ( _event: Electron.IpcMainInvokeEvent, @@ -23,12 +24,21 @@ const resetGameAchievements = async ( } } - await gameAchievementRepository.update( - { objectId: game.objectID }, - { - unlockedAchievements: null, - } - ); + const levelKey = levelKeys.game(game.shop, game.objectID); + + await gameAchievementsSublevel + .get(levelKey) + .then(async (gameAchievements) => { + if (gameAchievements) { + await gameAchievementsSublevel.put( + levelKeys.game(game.shop, game.objectID), + { + ...gameAchievements, + unlockedAchievements: [], + } + ); + } + }); await HydraApi.delete(`/profile/games/achievements/${game.remoteId}`).then( () => diff --git a/src/main/events/misc/open-checkout.ts b/src/main/events/misc/open-checkout.ts index 76e7fe09..95d76d5b 100644 --- a/src/main/events/misc/open-checkout.ts +++ b/src/main/events/misc/open-checkout.ts @@ -3,7 +3,7 @@ import { registerEvent } from "../register-event"; import { Crypto, HydraApi } from "@main/services"; import { db } from "@main/level"; import type { Auth } from "@types"; -import { levelKeys } from "@main/level/sublevels/keys"; +import { levelKeys } from "@main/level"; const openCheckout = async (_event: Electron.IpcMainInvokeEvent) => { const auth = await db.get(levelKeys.auth, { diff --git a/src/main/events/user/get-unlocked-achievements.ts b/src/main/events/user/get-unlocked-achievements.ts index ffa25399..78820a94 100644 --- a/src/main/events/user/get-unlocked-achievements.ts +++ b/src/main/events/user/get-unlocked-achievements.ts @@ -1,19 +1,17 @@ -import type { GameShop, UnlockedAchievement, UserAchievement } from "@types"; +import type { GameShop, UserAchievement } from "@types"; import { registerEvent } from "../register-event"; -import { - gameAchievementRepository, - userPreferencesRepository, -} from "@main/repository"; +import { userPreferencesRepository } from "@main/repository"; import { getGameAchievementData } from "@main/services/achievements/get-game-achievement-data"; +import { gameAchievementsSublevel, levelKeys } from "@main/level"; export const getUnlockedAchievements = async ( objectId: string, shop: GameShop, useCachedData: boolean ): Promise => { - const cachedAchievements = await gameAchievementRepository.findOne({ - where: { objectId, shop }, - }); + const cachedAchievements = await gameAchievementsSublevel.get( + levelKeys.game(shop, objectId) + ); const userPreferences = await userPreferencesRepository.findOne({ where: { id: 1 }, @@ -25,12 +23,10 @@ export const getUnlockedAchievements = async ( const achievementsData = await getGameAchievementData( objectId, shop, - useCachedData ? cachedAchievements : null + useCachedData ); - const unlockedAchievements = JSON.parse( - cachedAchievements?.unlockedAchievements || "[]" - ) as UnlockedAchievement[]; + const unlockedAchievements = cachedAchievements?.unlockedAchievements ?? []; return achievementsData .map((achievementData) => { diff --git a/src/main/events/user/get-user-friends.ts b/src/main/events/user/get-user-friends.ts index 7c308506..aefc7052 100644 --- a/src/main/events/user/get-user-friends.ts +++ b/src/main/events/user/get-user-friends.ts @@ -2,7 +2,7 @@ import { db } from "@main/level"; import { registerEvent } from "../register-event"; import { HydraApi } from "@main/services"; import type { User, UserFriends } from "@types"; -import { levelKeys } from "@main/level/sublevels/keys"; +import { levelKeys } from "@main/level/sublevels"; export const getUserFriends = async ( userId: string, diff --git a/src/main/level/sublevels/game-achievements.ts b/src/main/level/sublevels/game-achievements.ts new file mode 100644 index 00000000..4b1fa0c8 --- /dev/null +++ b/src/main/level/sublevels/game-achievements.ts @@ -0,0 +1,11 @@ +import type { GameAchievement } from "@types"; + +import { db } from "../level"; +import { levelKeys } from "./keys"; + +export const gameAchievementsSublevel = db.sublevel( + levelKeys.gameAchievements, + { + valueEncoding: "json", + } +); diff --git a/src/main/level/sublevels/game-shop-cache.ts b/src/main/level/sublevels/game-shop-cache.ts new file mode 100644 index 00000000..8187e5c0 --- /dev/null +++ b/src/main/level/sublevels/game-shop-cache.ts @@ -0,0 +1,11 @@ +import type { ShopDetails } from "@types"; + +import { db } from "../level"; +import { levelKeys } from "./keys"; + +export const gamesShopCacheSublevel = db.sublevel( + levelKeys.gameShopCache, + { + valueEncoding: "json", + } +); diff --git a/src/main/level/sublevels/games.ts b/src/main/level/sublevels/games.ts index bc0cad30..ce7492f1 100644 --- a/src/main/level/sublevels/games.ts +++ b/src/main/level/sublevels/games.ts @@ -1,4 +1,5 @@ -import { Game } from "@types"; +import type { Game } from "@types"; + import { db } from "../level"; import { levelKeys } from "./keys"; diff --git a/src/main/level/sublevels/index.ts b/src/main/level/sublevels/index.ts index 9d316e1a..ce61c4e2 100644 --- a/src/main/level/sublevels/index.ts +++ b/src/main/level/sublevels/index.ts @@ -1 +1,5 @@ export * from "./games"; +export * from "./game-shop-cache"; +export * from "./game-achievements"; + +export * from "./keys"; diff --git a/src/main/level/sublevels/keys.ts b/src/main/level/sublevels/keys.ts index 6bb54c4a..f2bb6f3c 100644 --- a/src/main/level/sublevels/keys.ts +++ b/src/main/level/sublevels/keys.ts @@ -5,4 +5,8 @@ export const levelKeys = { game: (shop: GameShop, objectId: string) => `${shop}:${objectId}`, user: "user", auth: "auth", + gameShopCache: "gameShopCache", + gameShopCacheItem: (shop: GameShop, objectId: string, language: string) => + `${shop}:${objectId}:${language}`, + gameAchievements: "gameAchievements", }; diff --git a/src/main/repository.ts b/src/main/repository.ts index ef120f7e..5bbfaf9f 100644 --- a/src/main/repository.ts +++ b/src/main/repository.ts @@ -1,20 +1,9 @@ import { dataSource } from "./data-source"; -import { - DownloadQueue, - Game, - GameShopCache, - UserPreferences, - GameAchievement, -} from "@main/entity"; +import { DownloadQueue, Game, UserPreferences } from "@main/entity"; export const gameRepository = dataSource.getRepository(Game); export const userPreferencesRepository = dataSource.getRepository(UserPreferences); -export const gameShopCacheRepository = dataSource.getRepository(GameShopCache); - export const downloadQueueRepository = dataSource.getRepository(DownloadQueue); - -export const gameAchievementRepository = - dataSource.getRepository(GameAchievement); diff --git a/src/main/services/achievements/get-game-achievement-data.ts b/src/main/services/achievements/get-game-achievement-data.ts index daac7e11..2dc643c1 100644 --- a/src/main/services/achievements/get-game-achievement-data.ts +++ b/src/main/services/achievements/get-game-achievement-data.ts @@ -1,40 +1,36 @@ -import { - gameAchievementRepository, - userPreferencesRepository, -} from "@main/repository"; +import { userPreferencesRepository } from "@main/repository"; import { HydraApi } from "../hydra-api"; -import type { AchievementData, GameShop } from "@types"; +import type { GameShop, SteamAchievement } from "@types"; import { UserNotLoggedInError } from "@shared"; import { logger } from "../logger"; -import { GameAchievement } from "@main/entity"; +import { gameAchievementsSublevel, levelKeys } from "@main/level"; export const getGameAchievementData = async ( objectId: string, shop: GameShop, - cachedAchievements: GameAchievement | null + useCachedData: boolean ) => { - if (cachedAchievements && cachedAchievements.achievements) { - return JSON.parse(cachedAchievements.achievements) as AchievementData[]; - } + const cachedAchievements = await gameAchievementsSublevel.get( + levelKeys.game(shop, objectId) + ); + + if (cachedAchievements && useCachedData) + return cachedAchievements.achievements; const userPreferences = await userPreferencesRepository.findOne({ where: { id: 1 }, }); - return HydraApi.get("/games/achievements", { + return HydraApi.get("/games/achievements", { shop, objectId, language: userPreferences?.language || "en", }) - .then((achievements) => { - gameAchievementRepository.upsert( - { - objectId, - shop, - achievements: JSON.stringify(achievements), - }, - ["objectId", "shop"] - ); + .then(async (achievements) => { + await gameAchievementsSublevel.put(levelKeys.game(shop, objectId), { + unlockedAchievements: cachedAchievements?.unlockedAchievements ?? [], + achievements, + }); return achievements; }) @@ -42,15 +38,9 @@ export const getGameAchievementData = async ( if (err instanceof UserNotLoggedInError) { throw err; } + logger.error("Failed to get game achievements", err); - return gameAchievementRepository - .findOne({ - where: { objectId, shop }, - }) - .then((gameAchievements) => { - return JSON.parse( - gameAchievements?.achievements || "[]" - ) as AchievementData[]; - }); + + return []; }); }; diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index dd8c877d..ac2f69d1 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -1,8 +1,5 @@ -import { - gameAchievementRepository, - userPreferencesRepository, -} from "@main/repository"; -import type { AchievementData, GameShop, UnlockedAchievement } from "@types"; +import { userPreferencesRepository } from "@main/repository"; +import type { GameShop, UnlockedAchievement } from "@types"; import { WindowManager } from "../window-manager"; import { HydraApi } from "../hydra-api"; import { getUnlockedAchievements } from "@main/events/user/get-unlocked-achievements"; @@ -10,33 +7,36 @@ import { Game } from "@main/entity"; import { publishNewAchievementNotification } from "../notifications"; import { SubscriptionRequiredError } from "@shared"; import { achievementsLogger } from "../logger"; +import { gameAchievementsSublevel, levelKeys } from "@main/level"; const saveAchievementsOnLocal = async ( objectId: string, shop: GameShop, - achievements: UnlockedAchievement[], + unlockedAchievements: UnlockedAchievement[], sendUpdateEvent: boolean ) => { - return gameAchievementRepository - .upsert( - { - objectId, - shop, - unlockedAchievements: JSON.stringify(achievements), - }, - ["objectId", "shop"] - ) - .then(() => { - if (!sendUpdateEvent) return; + const levelKey = levelKeys.game(shop, objectId); - return getUnlockedAchievements(objectId, shop, true) - .then((achievements) => { - WindowManager.mainWindow?.webContents.send( - `on-update-achievements-${objectId}-${shop}`, - achievements - ); - }) - .catch(() => {}); + return gameAchievementsSublevel + .get(levelKey) + .then(async (gameAchievement) => { + if (gameAchievement) { + await gameAchievementsSublevel.put(levelKey, { + ...gameAchievement, + unlockedAchievements: unlockedAchievements, + }); + + if (!sendUpdateEvent) return; + + return getUnlockedAchievements(objectId, shop, true) + .then((achievements) => { + WindowManager.mainWindow?.webContents.send( + `on-update-achievements-${objectId}-${shop}`, + achievements + ); + }) + .catch(() => {}); + } }); }; @@ -46,22 +46,12 @@ export const mergeAchievements = async ( publishNotification: boolean ) => { const [localGameAchievement, userPreferences] = await Promise.all([ - gameAchievementRepository.findOne({ - where: { - objectId: game.objectID, - shop: game.shop, - }, - }), + gameAchievementsSublevel.get(levelKeys.game(game.shop, game.objectID)), userPreferencesRepository.findOne({ where: { id: 1 } }), ]); - const achievementsData = JSON.parse( - localGameAchievement?.achievements || "[]" - ) as AchievementData[]; - - const unlockedAchievements = JSON.parse( - localGameAchievement?.unlockedAchievements || "[]" - ).filter((achievement) => achievement.name) as UnlockedAchievement[]; + const achievementsData = localGameAchievement?.achievements ?? []; + const unlockedAchievements = localGameAchievement?.unlockedAchievements ?? []; const newAchievementsMap = new Map( achievements.reverse().map((achievement) => { diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 6cf9a8af..5f7a5034 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -10,7 +10,7 @@ import { appVersion } from "@main/constants"; import { getUserData } from "./user/get-user-data"; import { isFuture, isToday } from "date-fns"; import { db } from "@main/level"; -import { levelKeys } from "@main/level/sublevels/keys"; +import { levelKeys } from "@main/level/sublevels"; import type { Auth, User } from "@types"; import { Crypto } from "./crypto"; diff --git a/src/main/services/user/get-user-data.ts b/src/main/services/user/get-user-data.ts index e6cf1c71..ed07c61e 100644 --- a/src/main/services/user/get-user-data.ts +++ b/src/main/services/user/get-user-data.ts @@ -3,7 +3,7 @@ import { HydraApi } from "../hydra-api"; import { UserNotLoggedInError } from "@shared"; import { logger } from "../logger"; import { db } from "@main/level"; -import { levelKeys } from "@main/level/sublevels/keys"; +import { levelKeys } from "@main/level/sublevels"; export const getUserData = async () => { return HydraApi.get(`/profile/me`) diff --git a/src/renderer/src/components/sidebar/sidebar.tsx b/src/renderer/src/components/sidebar/sidebar.tsx index 355d04b2..ae22f552 100644 --- a/src/renderer/src/components/sidebar/sidebar.tsx +++ b/src/renderer/src/components/sidebar/sidebar.tsx @@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useLocation, useNavigate } from "react-router-dom"; -import type { LibraryGame } from "@types"; +import type { Game } from "@types"; import { TextField } from "@renderer/components"; import { @@ -35,7 +35,7 @@ export function Sidebar() { const { library, updateLibrary } = useLibrary(); const navigate = useNavigate(); - const [filteredLibrary, setFilteredLibrary] = useState([]); + const [filteredLibrary, setFilteredLibrary] = useState([]); const [isResizing, setIsResizing] = useState(false); const [sidebarWidth, setSidebarWidth] = useState( @@ -117,7 +117,7 @@ export function Sidebar() { }; }, [isResizing]); - const getGameTitle = (game: LibraryGame) => { + const getGameTitle = (game: Game) => { if (lastPacket?.game.id === game.id) { return t("downloading", { title: game.title, @@ -140,10 +140,7 @@ export function Sidebar() { } }; - const handleSidebarGameClick = ( - event: React.MouseEvent, - game: LibraryGame - ) => { + const handleSidebarGameClick = (event: React.MouseEvent, game: Game) => { const path = buildGameDetailsPath({ ...game, objectId: game.objectID, diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 88f3297f..2ee60347 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -2,7 +2,6 @@ import type { CatalogueCategory } from "@shared"; import type { AppUpdaterEvent, Game, - LibraryGame, GameShop, HowLongToBeatCategory, ShopDetails, @@ -23,7 +22,6 @@ import type { UserStats, UserDetails, FriendRequestSync, - GameAchievement, GameArtifact, LudusaviBackup, UserAchievement, @@ -77,7 +75,7 @@ declare global { onUpdateAchievements: ( objectId: string, shop: GameShop, - cb: (achievements: GameAchievement[]) => void + cb: (achievements: UserAchievement[]) => void ) => () => Electron.IpcRenderer; getPublishers: () => Promise; getDevelopers: () => Promise; @@ -102,7 +100,7 @@ declare global { winePrefixPath: string | null ) => Promise; verifyExecutablePathInUse: (executablePath: string) => Promise; - getLibrary: () => Promise; + getLibrary: () => Promise; openGameInstaller: (gameId: number) => Promise; openGameInstallerPath: (gameId: number) => Promise; openGameExecutablePath: (gameId: number) => Promise; diff --git a/src/renderer/src/features/library-slice.ts b/src/renderer/src/features/library-slice.ts index 6c95aa79..f536ace7 100644 --- a/src/renderer/src/features/library-slice.ts +++ b/src/renderer/src/features/library-slice.ts @@ -1,10 +1,10 @@ import { createSlice } from "@reduxjs/toolkit"; import type { PayloadAction } from "@reduxjs/toolkit"; -import type { LibraryGame } from "@types"; +import type { Game } from "@types"; export interface LibraryState { - value: LibraryGame[]; + value: Game[]; } const initialState: LibraryState = { diff --git a/src/renderer/src/pages/achievements/achievement-list.tsx b/src/renderer/src/pages/achievements/achievement-list.tsx index ef178b50..6066f241 100644 --- a/src/renderer/src/pages/achievements/achievement-list.tsx +++ b/src/renderer/src/pages/achievements/achievement-list.tsx @@ -47,7 +47,14 @@ export function AchievementList({ achievements }: AchievementListProps) {

{achievement.description}

-
+
{achievement.points != undefined ? (
void; openGameInstaller: (gameId: number) => void; @@ -65,7 +65,7 @@ export function DownloadGroup({ resumeSeeding, } = useDownload(); - const getFinalDownloadSize = (game: LibraryGame) => { + const getFinalDownloadSize = (game: Game) => { const isGameDownloading = lastPacket?.game.id === game.id; if (game.fileSize) return formatBytes(game.fileSize); @@ -86,7 +86,7 @@ export function DownloadGroup({ return map; }, [seedingStatus]); - const getGameInfo = (game: LibraryGame) => { + const getGameInfo = (game: Game) => { const isGameDownloading = lastPacket?.game.id === game.id; const finalDownloadSize = getFinalDownloadSize(game); const seedingStatus = seedingMap.get(game.id); @@ -165,7 +165,7 @@ export function DownloadGroup({ return

{t(game.status as string)}

; }; - const getGameActions = (game: LibraryGame): DropdownMenuItem[] => { + const getGameActions = (game: Game): DropdownMenuItem[] => { const isGameDownloading = lastPacket?.game.id === game.id; const deleting = isGameDeleting(game.id); diff --git a/src/renderer/src/pages/downloads/downloads.tsx b/src/renderer/src/pages/downloads/downloads.tsx index 5c5a121a..41dbae90 100644 --- a/src/renderer/src/pages/downloads/downloads.tsx +++ b/src/renderer/src/pages/downloads/downloads.tsx @@ -7,7 +7,7 @@ import { BinaryNotFoundModal } from "../shared-modals/binary-not-found-modal"; import * as styles from "./downloads.css"; import { DeleteGameModal } from "./delete-game-modal"; import { DownloadGroup } from "./download-group"; -import type { LibraryGame, SeedingStatus } from "@types"; +import type { Game, SeedingStatus } from "@types"; import { orderBy } from "lodash-es"; import { ArrowDownIcon } from "@primer/octicons-react"; @@ -49,8 +49,8 @@ export default function Downloads() { setShowDeleteModal(true); }; - const libraryGroup: Record = useMemo(() => { - const initialValue: Record = { + const libraryGroup: Record = useMemo(() => { + const initialValue: Record = { downloading: [], queued: [], complete: [], diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index 7787b22a..d3a65ae5 100644 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -23,7 +23,7 @@ import { buildGameAchievementPath } from "@renderer/helpers"; import { SPACING_UNIT } from "@renderer/theme.css"; import { useSubscription } from "@renderer/hooks/use-subscription"; -const fakeAchievements: UserAchievement[] = [ +const achievementsPlaceholder: UserAchievement[] = [ { displayName: "Timber!!", name: "", @@ -140,7 +140,7 @@ export function Sidebar() {

{t("sign_in_to_see_achievements")}

@@ -264,7 +237,12 @@ export function Sidebar() { {filteredLibrary .filter((game) => !game.favorite) .map((game) => ( - + ))} From 0481a08dd38b41f41f735ff33bc3948d771e3af6 Mon Sep 17 00:00:00 2001 From: Leandro Perin Date: Tue, 4 Feb 2025 20:20:32 -0300 Subject: [PATCH 222/291] readonly props --- .../src/components/sidebar/sidebar-game-item.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/renderer/src/components/sidebar/sidebar-game-item.tsx b/src/renderer/src/components/sidebar/sidebar-game-item.tsx index 24ec296f..0672f847 100644 --- a/src/renderer/src/components/sidebar/sidebar-game-item.tsx +++ b/src/renderer/src/components/sidebar/sidebar-game-item.tsx @@ -3,15 +3,17 @@ import { LibraryGame } from "@types"; import cn from "classnames"; import { useLocation } from "react-router-dom"; +interface SidebarGameItemProps { + game: LibraryGame; + handleSidebarGameClick: (event: React.MouseEvent, game: LibraryGame) => void; + getGameTitle: (game: LibraryGame) => string; +} + export function SidebarGameItem({ game, handleSidebarGameClick, getGameTitle, -}: { - game: LibraryGame; - handleSidebarGameClick: (event: React.MouseEvent, game: LibraryGame) => void; - getGameTitle: (game: LibraryGame) => string; -}) { +}: Readonly) { const location = useLocation(); return ( From 0f0e27e2e556d5e888dd19dbc4695d22e1bb95b7 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 15 Feb 2025 19:28:38 +0000 Subject: [PATCH 223/291] feat: removing crypto from level --- src/main/events/auth/get-session-hash.ts | 5 +- src/main/events/misc/open-checkout.ts | 4 +- .../user-preferences/get-user-preferences.ts | 17 +--- src/main/main.ts | 20 ++--- src/main/services/crypto.ts | 28 ------- src/main/services/hydra-api.ts | 19 ++--- src/main/services/window-manager.ts | 2 +- src/renderer/src/app.tsx | 29 ++++--- src/renderer/src/main.tsx | 8 +- .../src/pages/downloads/delete-game-modal.tsx | 2 +- .../theme-editor.scss} | 11 ++- .../theme-editor.tsx} | 77 ++++++++++--------- 12 files changed, 88 insertions(+), 134 deletions(-) delete mode 100644 src/main/services/crypto.ts rename src/renderer/src/pages/{editor/editor.scss => theme-editor/theme-editor.scss} (88%) rename src/renderer/src/pages/{editor/editor.tsx => theme-editor/theme-editor.tsx} (80%) diff --git a/src/main/events/auth/get-session-hash.ts b/src/main/events/auth/get-session-hash.ts index c81e0965..02edebdc 100644 --- a/src/main/events/auth/get-session-hash.ts +++ b/src/main/events/auth/get-session-hash.ts @@ -3,7 +3,6 @@ import jwt from "jsonwebtoken"; import { registerEvent } from "../register-event"; import { db, levelKeys } from "@main/level"; import type { Auth } from "@types"; -import { Crypto } from "@main/services"; const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => { const auth = await db.get(levelKeys.auth, { @@ -11,9 +10,7 @@ const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => { }); if (!auth) return null; - const payload = jwt.decode( - Crypto.decrypt(auth.accessToken) - ) as jwt.JwtPayload; + const payload = jwt.decode(auth.accessToken) as jwt.JwtPayload; if (!payload) return null; diff --git a/src/main/events/misc/open-checkout.ts b/src/main/events/misc/open-checkout.ts index 76316a6e..75b93f9d 100644 --- a/src/main/events/misc/open-checkout.ts +++ b/src/main/events/misc/open-checkout.ts @@ -1,6 +1,6 @@ import { shell } from "electron"; import { registerEvent } from "../register-event"; -import { Crypto, HydraApi } from "@main/services"; +import { HydraApi } from "@main/services"; import { db, levelKeys } from "@main/level"; import type { Auth } from "@types"; @@ -14,7 +14,7 @@ const openCheckout = async (_event: Electron.IpcMainInvokeEvent) => { } const paymentToken = await HydraApi.post("/auth/payment", { - refreshToken: Crypto.decrypt(auth.refreshToken), + refreshToken: auth.refreshToken, }).then((response) => response.accessToken); const params = new URLSearchParams({ diff --git a/src/main/events/user-preferences/get-user-preferences.ts b/src/main/events/user-preferences/get-user-preferences.ts index b40d6780..ba01d077 100644 --- a/src/main/events/user-preferences/get-user-preferences.ts +++ b/src/main/events/user-preferences/get-user-preferences.ts @@ -1,21 +1,10 @@ import { registerEvent } from "../register-event"; import { db, levelKeys } from "@main/level"; -import { Crypto } from "@main/services"; import type { UserPreferences } from "@types"; const getUserPreferences = async () => - db - .get(levelKeys.userPreferences, { - valueEncoding: "json", - }) - .then((userPreferences) => { - if (userPreferences?.realDebridApiToken) { - userPreferences.realDebridApiToken = Crypto.decrypt( - userPreferences.realDebridApiToken - ); - } - - return userPreferences; - }); + db.get(levelKeys.userPreferences, { + valueEncoding: "json", + }); registerEvent("getUserPreferences", getUserPreferences); diff --git a/src/main/main.ts b/src/main/main.ts index f7ad596d..8205deef 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,10 +1,4 @@ -import { - Crypto, - DownloadManager, - logger, - Ludusavi, - startMainLoop, -} from "./services"; +import { DownloadManager, logger, Ludusavi, startMainLoop } from "./services"; import { RealDebridClient } from "./services/download/real-debrid"; import { HydraApi } from "./services/hydra-api"; import { uploadGamesBatch } from "./services/library-sync"; @@ -27,9 +21,7 @@ const loadState = async (userPreferences: UserPreferences | null) => { Aria2.spawn(); if (userPreferences?.realDebridApiToken) { - RealDebridClient.authorize( - Crypto.decrypt(userPreferences.realDebridApiToken) - ); + RealDebridClient.authorize(userPreferences.realDebridApiToken); } Ludusavi.addManifestToLudusaviConfig(); @@ -106,9 +98,7 @@ const migrateFromSqlite = async () => { await db.put(levelKeys.userPreferences, { ...rest, - realDebridApiToken: realDebridApiToken - ? Crypto.encrypt(realDebridApiToken) - : null, + realDebridApiToken, preferQuitInsteadOfHiding: rest.preferQuitInsteadOfHiding === 1, runAtStartup: rest.runAtStartup === 1, startMinimized: rest.startMinimized === 1, @@ -171,8 +161,8 @@ const migrateFromSqlite = async () => { await db.put( levelKeys.auth, { - accessToken: Crypto.encrypt(users[0].accessToken), - refreshToken: Crypto.encrypt(users[0].refreshToken), + accessToken: users[0].accessToken, + refreshToken: users[0].refreshToken, tokenExpirationTimestamp: users[0].tokenExpirationTimestamp, }, { diff --git a/src/main/services/crypto.ts b/src/main/services/crypto.ts deleted file mode 100644 index 63a50668..00000000 --- a/src/main/services/crypto.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { safeStorage } from "electron"; -import { logger } from "./logger"; - -export class Crypto { - public static encrypt(str: string) { - if (safeStorage.isEncryptionAvailable()) { - return safeStorage.encryptString(str).toString("base64"); - } else { - logger.warn( - "Encrypt method returned raw string because encryption is not available" - ); - - return str; - } - } - - public static decrypt(b64: string) { - if (safeStorage.isEncryptionAvailable()) { - return safeStorage.decryptString(Buffer.from(b64, "base64")); - } else { - logger.warn( - "Decrypt method returned raw string because encryption is not available" - ); - - return b64; - } - } -} diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 4d5623a0..1c67f729 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -12,7 +12,6 @@ import { isFuture, isToday } from "date-fns"; import { db } from "@main/level"; import { levelKeys } from "@main/level/sublevels"; import type { Auth, User } from "@types"; -import { Crypto } from "./crypto"; interface HydraApiOptions { needsAuth?: boolean; @@ -32,7 +31,9 @@ export class HydraApi { private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5; // 5 minutes private static readonly ADD_LOG_INTERCEPTOR = true; - private static secondsToMilliseconds = (seconds: number) => seconds * 1000; + private static secondsToMilliseconds(seconds: number) { + return seconds * 1000; + } private static userAuth: HydraApiUserAuth = { authToken: "", @@ -80,8 +81,8 @@ export class HydraApi { db.put( levelKeys.auth, { - accessToken: Crypto.encrypt(accessToken), - refreshToken: Crypto.encrypt(refreshToken), + accessToken, + refreshToken, tokenExpirationTimestamp, }, { valueEncoding: "json" } @@ -194,12 +195,8 @@ export class HydraApi { const user = result.at(1) as User | undefined; this.userAuth = { - authToken: userAuth?.accessToken - ? Crypto.decrypt(userAuth.accessToken) - : "", - refreshToken: userAuth?.refreshToken - ? Crypto.decrypt(userAuth.refreshToken) - : "", + authToken: userAuth?.accessToken ?? "", + refreshToken: userAuth?.refreshToken ?? "", expirationTimestamp: userAuth?.tokenExpirationTimestamp ?? 0, subscription: user?.subscription ? { expiresAt: user.subscription?.expiresAt } @@ -248,7 +245,7 @@ export class HydraApi { levelKeys.auth, { ...auth, - accessToken: Crypto.encrypt(accessToken), + accessToken, tokenExpirationTimestamp, }, { valueEncoding: "json" } diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 3934144a..3d31284d 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -234,7 +234,7 @@ export class WindowManager { if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { editorWindow.loadURL( - `${process.env["ELECTRON_RENDERER_URL"]}#/editor?themeId=${themeId}` + `${process.env["ELECTRON_RENDERER_URL"]}#/theme-editor?themeId=${themeId}` ); } else { editorWindow.loadFile(path.join(__dirname, "../renderer/index.html"), { diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 8a681b7e..a9ec7923 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -30,7 +30,6 @@ import { HydraCloudModal } from "./pages/shared-modals/hydra-cloud/hydra-cloud-m import { injectCustomCss } from "./helpers"; import "./app.scss"; -import { Theme } from "@types"; export interface AppProps { children: React.ReactNode; @@ -214,22 +213,22 @@ export function App() { const id = crypto.randomUUID(); const channel = new BroadcastChannel(`download_sources:sync:${id}`); - channel.onmessage = (event: MessageEvent) => { + channel.onmessage = async (event: MessageEvent) => { const newRepacksCount = event.data; window.electron.publishNewRepacksNotification(newRepacksCount); updateRepacks(); - downloadSourcesTable.toArray().then((downloadSources) => { - downloadSources - .filter((source) => !source.fingerprint) - .forEach((downloadSource) => { - window.electron - .putDownloadSource(downloadSource.objectIds) - .then(({ fingerprint }) => { - downloadSourcesTable.update(downloadSource.id, { fingerprint }); - }); - }); - }); + const downloadSources = await downloadSourcesTable.toArray(); + + downloadSources + .filter((source) => !source.fingerprint) + .forEach(async (downloadSource) => { + const { fingerprint } = await window.electron.putDownloadSource( + downloadSource.objectIds + ); + + downloadSourcesTable.update(downloadSource.id, { fingerprint }); + }); }; downloadSourcesWorker.postMessage(["SYNC_DOWNLOAD_SOURCES", id]); @@ -237,9 +236,9 @@ export function App() { useEffect(() => { const loadAndApplyTheme = async () => { - const activeTheme: Theme = await window.electron.getActiveCustomTheme(); + const activeTheme = await window.electron.getActiveCustomTheme(); - if (activeTheme.code) { + if (activeTheme?.code) { injectCustomCss(activeTheme.code); } }; diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index 509d744d..80c4317c 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -33,7 +33,9 @@ const Profile = React.lazy(() => import("./pages/profile/profile")); const Achievements = React.lazy( () => import("./pages/achievements/achievements") ); -const Editor = React.lazy(() => import("./pages/editor/editor")); +const ThemeEditor = React.lazy( + () => import("./pages/theme-editor/theme-editor") +); import * as Sentry from "@sentry/react"; @@ -107,8 +109,8 @@ ReactDOM.createRoot(document.getElementById("root")!).render( } + path="/theme-editor" + element={} /> diff --git a/src/renderer/src/pages/downloads/delete-game-modal.tsx b/src/renderer/src/pages/downloads/delete-game-modal.tsx index e9486652..7f4a530e 100644 --- a/src/renderer/src/pages/downloads/delete-game-modal.tsx +++ b/src/renderer/src/pages/downloads/delete-game-modal.tsx @@ -12,7 +12,7 @@ export function DeleteGameModal({ onClose, visible, deleteGame, -}: DeleteGameModalProps) { +}: Readonly) { const { t } = useTranslation("downloads"); const handleDeleteGame = () => { diff --git a/src/renderer/src/pages/editor/editor.scss b/src/renderer/src/pages/theme-editor/theme-editor.scss similarity index 88% rename from src/renderer/src/pages/editor/editor.scss rename to src/renderer/src/pages/theme-editor/theme-editor.scss index d522c8c5..1f436417 100644 --- a/src/renderer/src/pages/editor/editor.scss +++ b/src/renderer/src/pages/theme-editor/theme-editor.scss @@ -1,22 +1,25 @@ -@use "../../scss/globals.scss" as globals; +@use "../../scss/globals.scss"; -.editor { +.theme-editor { display: flex; flex-direction: column; height: 100%; width: 100%; &__header { - height: 35px; display: flex; align-items: center; - padding: 10px; + padding: calc(globals.$spacing-unit * 2); background-color: globals.$dark-background-color; font-size: 8px; z-index: 50; -webkit-app-region: drag; gap: 8px; + &--darwin { + padding-top: calc(globals.$spacing-unit * 6); + } + h1 { margin: 0; line-height: 1; diff --git a/src/renderer/src/pages/editor/editor.tsx b/src/renderer/src/pages/theme-editor/theme-editor.tsx similarity index 80% rename from src/renderer/src/pages/editor/editor.tsx rename to src/renderer/src/pages/theme-editor/theme-editor.tsx index 95210b2f..b69b8261 100644 --- a/src/renderer/src/pages/editor/editor.tsx +++ b/src/renderer/src/pages/theme-editor/theme-editor.tsx @@ -1,5 +1,5 @@ -import { useEffect, useState } from "react"; -import "./editor.scss"; +import { useCallback, useEffect, useState } from "react"; +import "./theme-editor.scss"; import Editor from "@monaco-editor/react"; import { Theme } from "@types"; import { useSearchParams } from "react-router-dom"; @@ -10,8 +10,9 @@ import { ProjectRoadmapIcon, } from "@primer/octicons-react"; import { useTranslation } from "react-i18next"; +import cn from "classnames"; -const EditorPage = () => { +export default function ThemeEditor() { const [searchParams] = useSearchParams(); const [theme, setTheme] = useState(null); const [code, setCode] = useState(""); @@ -37,29 +38,7 @@ const EditorPage = () => { } }, [themeId]); - useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - if ((event.ctrlKey || event.metaKey) && event.key === "s") { - event.preventDefault(); - handleSave(); - } - }; - - window.addEventListener("keydown", handleKeyDown); - - return () => { - window.removeEventListener("keydown", handleKeyDown); - }; - }, [code, theme]); - - const handleEditorChange = (value: string | undefined) => { - if (value !== undefined) { - setCode(value); - setHasUnsavedChanges(true); - } - }; - - const handleSave = async () => { + const handleSave = useCallback(async () => { if (theme) { const updatedTheme = { ...theme, @@ -74,13 +53,41 @@ const EditorPage = () => { window.electron.injectCSS(code); } } + }, [code, theme]); + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if ((event.ctrlKey || event.metaKey) && event.key === "s") { + event.preventDefault(); + handleSave(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, [code, handleSave, theme]); + + const handleEditorChange = (value: string | undefined) => { + if (value !== undefined) { + setCode(value); + setHasUnsavedChanges(true); + } }; return ( -
-
+
+

{theme?.name}

- {hasUnsavedChanges &&
} + {hasUnsavedChanges && ( +
+ )}
{activeTab === "code" && ( @@ -100,15 +107,15 @@ const EditorPage = () => { )} {activeTab === "info" && ( -
+
entao mano eu ate fiz isso aqui mas tava feio dms ai deu vergonha e removi kkkk
)} -
-
-
+
+
+
); -}; - -export default EditorPage; +} From ec638d1a7a1646ac517f8d8c67b122e9de4ed9f4 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sat, 15 Feb 2025 20:42:26 -0300 Subject: [PATCH 224/291] refactor: simplify theme structure and remove color-related code --- .../aparence/components/theme-card.tsx | 11 ---- .../aparence/modals/add-theme-modal.tsx | 5 -- .../src/pages/theme-editor/theme-editor.scss | 4 +- .../src/pages/theme-editor/theme-editor.tsx | 51 +++---------------- src/types/theme.types.ts | 18 ------- 5 files changed, 9 insertions(+), 80 deletions(-) diff --git a/src/renderer/src/pages/settings/aparence/components/theme-card.tsx b/src/renderer/src/pages/settings/aparence/components/theme-card.tsx index b8797b82..4bc9698f 100644 --- a/src/renderer/src/pages/settings/aparence/components/theme-card.tsx +++ b/src/renderer/src/pages/settings/aparence/components/theme-card.tsx @@ -81,17 +81,6 @@ export const ThemeCard = ({ theme, onListUpdated }: ThemeCardProps) => { >
{theme.name}
- -
- {Object.entries(theme.colors).map(([key, color]) => ( -
- ))} -
{theme.authorName && ( diff --git a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx index ec2798ec..ac50eed9 100644 --- a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx @@ -41,11 +41,6 @@ export const AddThemeModal = ({ isActive: false, author: userDetails?.id || undefined, authorName: userDetails?.username || undefined, - colors: { - accent: "", - background: "", - surface: "", - }, code: "", createdAt: new Date(), updatedAt: new Date(), diff --git a/src/renderer/src/pages/theme-editor/theme-editor.scss b/src/renderer/src/pages/theme-editor/theme-editor.scss index 1f436417..8ba09958 100644 --- a/src/renderer/src/pages/theme-editor/theme-editor.scss +++ b/src/renderer/src/pages/theme-editor/theme-editor.scss @@ -9,7 +9,7 @@ &__header { display: flex; align-items: center; - padding: calc(globals.$spacing-unit * 2); + padding: calc(globals.$spacing-unit + 1px); background-color: globals.$dark-background-color; font-size: 8px; z-index: 50; @@ -47,7 +47,7 @@ &-actions { display: flex; flex-direction: row; - justify-content: space-between; + justify-content: flex-end; align-items: center; &__tabs { diff --git a/src/renderer/src/pages/theme-editor/theme-editor.tsx b/src/renderer/src/pages/theme-editor/theme-editor.tsx index b69b8261..68250467 100644 --- a/src/renderer/src/pages/theme-editor/theme-editor.tsx +++ b/src/renderer/src/pages/theme-editor/theme-editor.tsx @@ -4,11 +4,7 @@ import Editor from "@monaco-editor/react"; import { Theme } from "@types"; import { useSearchParams } from "react-router-dom"; import { Button } from "@renderer/components"; -import { - CheckIcon, - CodeIcon, - ProjectRoadmapIcon, -} from "@primer/octicons-react"; +import { CheckIcon } from "@primer/octicons-react"; import { useTranslation } from "react-i18next"; import cn from "classnames"; @@ -16,17 +12,12 @@ export default function ThemeEditor() { const [searchParams] = useSearchParams(); const [theme, setTheme] = useState(null); const [code, setCode] = useState(""); - const [activeTab, setActiveTab] = useState("code"); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const themeId = searchParams.get("themeId"); const { t } = useTranslation("settings"); - const handleTabChange = (tab: string) => { - setActiveTab(tab); - }; - useEffect(() => { if (themeId) { window.electron.getCustomThemeById(themeId).then((loadedTheme) => { @@ -90,12 +81,11 @@ export default function ThemeEditor() { )}
- {activeTab === "code" && ( - - )} - - {activeTab === "info" && ( -
- entao mano eu ate fiz isso aqui mas tava feio dms ai deu vergonha e - removi kkkk -
- )} + />
-
- - -
- From 543528bcce780478bab8dd47ae209962dde54ef3 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 15 Feb 2025 22:59:59 -0300 Subject: [PATCH 232/291] feat: i18n --- src/locales/en/translation.json | 6 +++--- src/locales/pt-BR/translation.json | 6 +++--- .../pages/settings/aparence/components/theme-actions.tsx | 2 +- .../src/pages/settings/aparence/modals/add-theme-modal.tsx | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index e05159c7..1f4c0104 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -310,9 +310,9 @@ "editor_tab_save": "Save", "web_store": "Web store", "clear_themes": "Clear", - "add_theme": "Add", - "add_theme_modal_title": "Create custom theme", - "add_theme_modal_description": "Create a new theme to customize Hydra's appearance", + "create_theme": "Create", + "create_theme_modal_title": "Create custom theme", + "create_theme_modal_description": "Create a new theme to customize Hydra's appearance", "theme_name": "Name", "insert_theme_name": "Insert theme name", "set_theme": "Set theme", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 3bc54a43..5b8bfedf 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -300,9 +300,9 @@ "editor_tab_save": "Salvar", "web_store": "Loja de temas", "clear_themes": "Limpar", - "add_theme": "Adicionar", - "add_theme_modal_title": "Criar tema customizado", - "add_theme_modal_description": "Criar novo tema para customizar a aparência do Hydra", + "create_theme": "Criar", + "create_theme_modal_title": "Criar tema customizado", + "create_theme_modal_description": "Criar novo tema para customizar a aparência do Hydra", "theme_name": "Nome", "insert_theme_name": "Insira o nome do tema", "set_theme": "Habilitar tema", diff --git a/src/renderer/src/pages/settings/aparence/components/theme-actions.tsx b/src/renderer/src/pages/settings/aparence/components/theme-actions.tsx index 842823b5..3f98d8cc 100644 --- a/src/renderer/src/pages/settings/aparence/components/theme-actions.tsx +++ b/src/renderer/src/pages/settings/aparence/components/theme-actions.tsx @@ -67,7 +67,7 @@ export const ThemeActions = ({ onClick={() => setAddThemeModalVisible(true)} > - {t("add_theme")} + {t("create_theme")}
diff --git a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx index ac50eed9..9b5a912f 100644 --- a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx @@ -56,8 +56,8 @@ export const AddThemeModal = ({ return (
@@ -72,7 +72,7 @@ export const AddThemeModal = ({ />
From 0511cc08c733004ddf3f681bd3561eaa895446a3 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 15 Feb 2025 23:27:18 -0300 Subject: [PATCH 233/291] fix: editor window use correct dev tools --- src/main/services/window-manager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 296ad4c1..45fd1097 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -251,18 +251,18 @@ export class WindowManager { editorWindow.once("ready-to-show", () => { editorWindow.show(); - WindowManager.mainWindow?.webContents.openDevTools(); + editorWindow.webContents.openDevTools(); }); editorWindow.webContents.on("before-input-event", (event, input) => { if (input.key === "F12") { event.preventDefault(); - this.mainWindow?.webContents.toggleDevTools(); + editorWindow.webContents.toggleDevTools(); } }); editorWindow.on("close", () => { - WindowManager.mainWindow?.webContents.closeDevTools(); + editorWindow.webContents.closeDevTools(); this.editorWindows.delete(themeId); }); } From 3271de09f891d7618c0caa8c884abad2df69a82c Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 15 Feb 2025 23:42:14 -0300 Subject: [PATCH 234/291] fix: dev tools --- src/main/services/window-manager.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 45fd1097..3da47644 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -251,18 +251,21 @@ export class WindowManager { editorWindow.once("ready-to-show", () => { editorWindow.show(); - editorWindow.webContents.openDevTools(); + this.mainWindow?.webContents.openDevTools(); + if (isStaging) { + editorWindow.webContents.openDevTools(); + } }); editorWindow.webContents.on("before-input-event", (event, input) => { if (input.key === "F12") { event.preventDefault(); - editorWindow.webContents.toggleDevTools(); + this.mainWindow?.webContents.toggleDevTools(); } }); editorWindow.on("close", () => { - editorWindow.webContents.closeDevTools(); + this.mainWindow?.webContents.closeDevTools(); this.editorWindows.delete(themeId); }); } From 39ceb8ee6e8047e3cb3e3586fee5f5d1ae640b50 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 15 Feb 2025 23:54:22 -0300 Subject: [PATCH 235/291] fix: editor window hash path --- src/main/services/window-manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 3da47644..f51d0e39 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -245,7 +245,7 @@ export class WindowManager { ); } else { editorWindow.loadFile(path.join(__dirname, "../renderer/index.html"), { - hash: `editor?themeId=${themeId}`, + hash: `theme-editor?themeId=${themeId}`, }); } From ba6eb2ecc8d03b71580d2320b9fd49f8bd2f6e68 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 16 Feb 2025 03:01:47 +0000 Subject: [PATCH 236/291] fix: fixing download cancel --- .../events/torrenting/cancel-game-download.ts | 4 +- .../services/download/download-manager.ts | 15 +++--- .../modals/download-settings-modal.tsx | 51 ++++++++++--------- 3 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/main/events/torrenting/cancel-game-download.ts b/src/main/events/torrenting/cancel-game-download.ts index 5d80337f..29648cb8 100644 --- a/src/main/events/torrenting/cancel-game-download.ts +++ b/src/main/events/torrenting/cancel-game-download.ts @@ -2,7 +2,7 @@ import { registerEvent } from "../register-event"; import { DownloadManager } from "@main/services"; import { GameShop } from "@types"; -import { downloadsSublevel, levelKeys } from "@main/level"; +import { levelKeys } from "@main/level"; const cancelGameDownload = async ( _event: Electron.IpcMainInvokeEvent, @@ -12,8 +12,6 @@ const cancelGameDownload = async ( const downloadKey = levelKeys.game(shop, objectId); await DownloadManager.cancelDownload(downloadKey); - - await downloadsSublevel.del(downloadKey); }; registerEvent("cancelGameDownload", cancelGameDownload); diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index 247d5c75..0dbd6129 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -228,14 +228,17 @@ export class DownloadManager { } static async cancelDownload(downloadKey = this.downloadingGameId) { - await PythonRPC.rpc.post("/action", { - action: "cancel", - game_id: downloadKey, - }); - - WindowManager.mainWindow?.setProgressBar(-1); + await PythonRPC.rpc + .post("/action", { + action: "cancel", + game_id: downloadKey, + }) + .catch((err) => { + logger.error("Failed to cancel game download", err); + }); if (downloadKey === this.downloadingGameId) { + WindowManager.mainWindow?.setProgressBar(-1); WindowManager.mainWindow?.webContents.send("on-download-progress", null); this.downloadingGameId = null; } diff --git a/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx b/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx index 214af1d1..f41c3216 100644 --- a/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx @@ -44,10 +44,9 @@ export function DownloadSettingsModal({ (state) => state.userPreferences.value ); - const getDiskFreeSpace = (path: string) => { - window.electron.getDiskFreeSpace(path).then((result) => { - setDiskFreeSpace(result.free); - }); + const getDiskFreeSpace = async (path: string) => { + const result = await window.electron.getDiskFreeSpace(path); + setDiskFreeSpace(result.free); }; const checkFolderWritePermission = useCallback( @@ -100,6 +99,7 @@ export function DownloadSettingsModal({ userPreferences?.downloadsPath, downloaders, userPreferences?.realDebridApiToken, + userPreferences?.torBoxApiToken, ]); const handleChooseDownloadsPath = async () => { @@ -155,25 +155,30 @@ export function DownloadSettingsModal({ {t("downloader")}
- {downloaders.map((downloader) => ( - - ))} + {downloaders.map((downloader) => { + const shouldDisableButton = + (downloader === Downloader.RealDebrid && + !userPreferences?.realDebridApiToken) || + (downloader === Downloader.TorBox && + !userPreferences?.torBoxApiToken); + + return ( + + ); + })}
From 22e567466f405b12cd9f4498dcdf8e21bdac902a Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 16 Feb 2025 03:45:29 +0000 Subject: [PATCH 237/291] fix: showing form errors when creating theme --- src/locales/en/translation.json | 3 +- src/locales/pt-BR/translation.json | 3 +- .../aparence/modals/add-theme-modal.tsx | 95 +++++++++++-------- 3 files changed, 59 insertions(+), 42 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 1f4c0104..ef4e3b56 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -327,7 +327,8 @@ "enable_torbox": "Enable Torbox", "torbox_description": "TorBox is your premium seedbox service rivaling even the best servers on the market.", "torbox_account_linked": "TorBox account linked", - "real_debrid_account_linked": "Real-Debrid account linked" + "real_debrid_account_linked": "Real-Debrid account linked", + "name_min_length": "Theme name must be at least 3 characters long" }, "notifications": { "download_complete": "Download complete", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 5b8bfedf..a8037b2d 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -317,7 +317,8 @@ "enable_torbox": "Habilitar Torbox", "torbox_description": "TorBox é o seu serviço de seedbox premium que rivaliza até com os melhores servidores do mercado.", "torbox_account_linked": "Conta do TorBox vinculada", - "real_debrid_account_linked": "Conta Real-Debrid associada" + "real_debrid_account_linked": "Conta Real-Debrid associada", + "name_min_length": "O nome do tema deve ter pelo menos 3 caracteres" }, "notifications": { "download_complete": "Download concluído", diff --git a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx index 9b5a912f..d78d5ddb 100644 --- a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx @@ -2,10 +2,14 @@ import { Modal } from "@renderer/components/modal/modal"; import { TextField } from "@renderer/components/text-field/text-field"; import { Button } from "@renderer/components/button/button"; import { useTranslation } from "react-i18next"; -import { useState } from "react"; import "./modals.scss"; import { useUserDetails } from "@renderer/hooks"; import { Theme } from "@types"; +import { useForm } from "react-hook-form"; + +import * as yup from "yup"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { useCallback } from "react"; interface AddThemeModalProps { visible: boolean; @@ -13,45 +17,54 @@ interface AddThemeModalProps { onThemeAdded: () => void; } -export const AddThemeModal = ({ +interface FormValues { + name: string; +} + +export function AddThemeModal({ visible, onClose, onThemeAdded, -}: AddThemeModalProps) => { +}: Readonly) { const { t } = useTranslation("settings"); const { userDetails } = useUserDetails(); - const [name, setName] = useState(""); - const [error, setError] = useState(""); - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === "Enter") { - handleSubmit(); - } - }; + const schema = yup.object({ + name: yup + .string() + .required(t("required_field")) + .min(3, t("name_min_length")), + }); - const handleSubmit = async () => { - if (!name || name.length < 3) { - setError(t("theme_name_error_hint")); - return; - } + const { + register, + handleSubmit, + reset, + formState: { isSubmitting, errors }, + } = useForm({ + resolver: yupResolver(schema), + }); - const theme: Theme = { - id: crypto.randomUUID(), - name, - isActive: false, - author: userDetails?.id || undefined, - authorName: userDetails?.username || undefined, - code: "", - createdAt: new Date(), - updatedAt: new Date(), - }; + const onSubmit = useCallback( + async (values: FormValues) => { + const theme: Theme = { + id: crypto.randomUUID(), + name: values.name, + isActive: false, + author: userDetails?.id, + authorName: userDetails?.username, + code: "", + createdAt: new Date(), + updatedAt: new Date(), + }; - await window.electron.addCustomTheme(theme); - setName(""); - setError(""); - onThemeAdded(); - onClose(); - }; + await window.electron.addCustomTheme(theme); + onThemeAdded(); + onClose(); + reset(); + }, + [onClose, onThemeAdded, userDetails?.id, userDetails?.username, reset] + ); return ( -
+
setName(e.target.value)} - hint={error} - error={!!error} - onKeyDown={handleKeyDown} + hint={errors.name?.message} + error={errors.name?.message} /> - -
+
); -}; +} From d7d88ecb8ca962933cc62699a4d5fc7bb88637da Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sun, 16 Feb 2025 01:27:15 -0300 Subject: [PATCH 238/291] feat: add deep link theme import functionality --- src/main/events/index.ts | 1 + src/main/events/themes/deeplink.ts | 64 ++++++------- src/main/events/themes/import-theme.ts | 12 +++ src/main/index.ts | 3 +- src/preload/index.ts | 6 ++ src/renderer/src/app.tsx | 28 +++++- src/renderer/src/declaration.d.ts | 3 + .../aparence/modals/import-theme-modal.tsx | 92 +++++++++++++++++++ 8 files changed, 174 insertions(+), 35 deletions(-) create mode 100644 src/main/events/themes/import-theme.ts create mode 100644 src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 80b02c35..f377cdba 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -87,6 +87,7 @@ import "./themes/get-custom-theme-by-id"; import "./themes/get-active-custom-theme"; import "./themes/css-injector"; import "./themes/close-editor-window"; +import "./themes/import-theme"; import { isPortableVersion } from "@main/helpers"; ipcMain.handle("ping", () => "pong"); diff --git a/src/main/events/themes/deeplink.ts b/src/main/events/themes/deeplink.ts index ca561701..47197a4f 100644 --- a/src/main/events/themes/deeplink.ts +++ b/src/main/events/themes/deeplink.ts @@ -1,38 +1,38 @@ -import { themes } from "@main/level/sublevels/themes"; -import { WindowManager } from "@main/services"; -import { Theme } from "@types"; +// import { themes } from "@main/level/sublevels/themes"; +// import { WindowManager } from "@main/services"; +// import { Theme } from "@types"; -export const handleDeepLinkTheme = async ( - themeName: string, - authorCode: string -) => { - const theme: Theme = { - id: crypto.randomUUID(), - name: themeName, - isActive: false, - author: authorCode, - authorName: "spectre", - code: `https://hydrathemes.shop/themes/${themeName}.css`, - createdAt: new Date(), - updatedAt: new Date(), - }; +// export const handleDeepLinkTheme = async ( +// themeName: string, +// authorCode: string +// ) => { +// const theme: Theme = { +// id: crypto.randomUUID(), +// name: themeName, +// isActive: false, +// author: authorCode, +// authorName: "spectre", +// code: `https://hydrathemes.shop/themes/${themeName}.css`, +// createdAt: new Date(), +// updatedAt: new Date(), +// }; - await themes.put(theme.id, theme); +// await themes.put(theme.id, theme); - const allThemes = await themes.values().all(); - const activeTheme = allThemes.find((theme: Theme) => theme.isActive); +// const allThemes = await themes.values().all(); +// const activeTheme = allThemes.find((theme: Theme) => theme.isActive); - if (activeTheme) { - await themes.put(activeTheme.id, { - ...activeTheme, - isActive: false, - }); - } +// if (activeTheme) { +// await themes.put(activeTheme.id, { +// ...activeTheme, +// isActive: false, +// }); +// } - WindowManager.mainWindow?.webContents.send("css-injected", theme.code); +// WindowManager.mainWindow?.webContents.send("css-injected", theme.code); - await themes.put(theme.id, { - ...theme, - isActive: true, - }); -}; +// await themes.put(theme.id, { +// ...theme, +// isActive: true, +// }); +// }; diff --git a/src/main/events/themes/import-theme.ts b/src/main/events/themes/import-theme.ts new file mode 100644 index 00000000..a5b06287 --- /dev/null +++ b/src/main/events/themes/import-theme.ts @@ -0,0 +1,12 @@ +import { registerEvent } from "../register-event"; +import { WindowManager } from "@main/services"; + +const importTheme = async ( + _event: Electron.IpcMainInvokeEvent, + theme: string, + author: string, +) => { + WindowManager.mainWindow?.webContents.send("import-theme", theme, author); +}; + +registerEvent("importTheme", importTheme); diff --git a/src/main/index.ts b/src/main/index.ts index 18423ee8..b289a442 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -10,7 +10,6 @@ import { PythonRPC } from "./services/python-rpc"; import { Aria2 } from "./services/aria2"; import { db, levelKeys } from "./level"; import { loadState } from "./main"; -import { handleDeepLinkTheme } from "./events/themes/deeplink"; const { autoUpdater } = updater; @@ -93,7 +92,7 @@ const handleDeepLinkPath = (uri?: string) => { const authorCode = url.searchParams.get("author"); if (themeName && authorCode) { - handleDeepLinkTheme(themeName, authorCode); + WindowManager.mainWindow?.webContents.send("import-theme", themeName, authorCode); } } } catch (error) { diff --git a/src/preload/index.ts b/src/preload/index.ts index 31ae002b..a247bd17 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -360,6 +360,12 @@ contextBridge.exposeInMainWorld("electron", { getCustomThemeById: (themeId: string) => ipcRenderer.invoke("getCustomThemeById", themeId), getActiveCustomTheme: () => ipcRenderer.invoke("getActiveCustomTheme"), + onImportTheme: (cb: (theme: string, author: string) => void) => { + const listener = (_event: Electron.IpcRendererEvent, theme: string, author: string) => + cb(theme, author); + ipcRenderer.on("import-theme", listener); + return () => ipcRenderer.removeListener("import-theme", listener); + }, /* Editor */ openEditorWindow: (themeId: string) => diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index daa93a6b..0fd6370c 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import achievementSound from "@renderer/assets/audio/achievement.wav"; import { Sidebar, BottomPanel, Header, Toast } from "@renderer/components"; @@ -30,6 +30,7 @@ import { HydraCloudModal } from "./pages/shared-modals/hydra-cloud/hydra-cloud-m import { injectCustomCss } from "./helpers"; import "./app.scss"; +import { ImportThemeModal } from "./pages/settings/aparence/modals/import-theme-modal"; export interface AppProps { children: React.ReactNode; @@ -38,6 +39,12 @@ export interface AppProps { export function App() { const contentRef = useRef(null); const { updateLibrary, library } = useLibrary(); + const [isImportThemeModalVisible, setIsImportThemeModalVisible] = useState(false); + const [importTheme, setImportTheme] = useState<{ + theme: string; + author: string; + } | null>(null); + const { t } = useTranslation("app"); @@ -271,6 +278,15 @@ export function App() { return () => unsubscribe(); }, []); + useEffect(() => { + const unsubscribe = window.electron.onImportTheme((theme, author) => { + setImportTheme({ theme, author }); + setIsImportThemeModalVisible(true); + }); + + return () => unsubscribe(); + }, []); + const handleToastClose = useCallback(() => { dispatch(closeToast()); }, [dispatch]); @@ -312,6 +328,16 @@ export function App() { /> )} + {importTheme && ( + setIsImportThemeModalVisible(false)} + onThemeImported={() => setIsImportThemeModalVisible(false)} + themeName={importTheme.theme} + authorCode={importTheme.author} + /> + )} +
diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 7141a619..62886deb 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -288,6 +288,9 @@ declare global { updateCustomTheme: (themeId: string, theme: Theme) => Promise; getCustomThemeById: (themeId: string) => Promise; getActiveCustomTheme: () => Promise; + onImportTheme: ( + cb: (theme: string, author: string) => void + ) => () => Electron.IpcRenderer; /* Editor */ openEditorWindow: (themeId: string) => Promise; diff --git a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx new file mode 100644 index 00000000..8431a647 --- /dev/null +++ b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx @@ -0,0 +1,92 @@ +import { Button } from "@renderer/components/button/button"; +import { Modal } from "@renderer/components/modal/modal"; +import { useTranslation } from "react-i18next"; +import "./modals.scss"; +import { Theme } from "@types"; +import { injectCustomCss, removeCustomCss } from "@renderer/helpers"; +import { useToast } from "@renderer/hooks"; + +interface ImportThemeModalProps { + visible: boolean; + onClose: () => void; + onThemeImported: () => void; + themeName: string; + authorCode: string; +} + +export const ImportThemeModal = ({ + visible, + onClose, + onThemeImported, + themeName, + authorCode, +}: ImportThemeModalProps) => { + const { t } = useTranslation("settings"); + const { showSuccessToast, showErrorToast } = useToast(); + + const handleImportTheme = async () => { + const theme: Theme = { + id: crypto.randomUUID(), + name: themeName, + isActive: false, + author: authorCode, + authorName: "spectre", + code: `https://hydrathemes.shop/themes/${themeName}.css`, + createdAt: new Date(), + updatedAt: new Date(), + }; + + try { + await window.electron.addCustomTheme(theme); + + const currentTheme = await window.electron.getCustomThemeById(theme.id); + + if (!currentTheme) return; + + const activeTheme = await window.electron.getActiveCustomTheme(); + + if (activeTheme) { + removeCustomCss(); + await window.electron.updateCustomTheme(activeTheme.id, { + ...activeTheme, + isActive: false, + }); + } + + if (currentTheme.code) { + injectCustomCss(currentTheme.code); + } + + await window.electron.updateCustomTheme(currentTheme.id, { + ...currentTheme, + isActive: true, + }); + onThemeImported(); + showSuccessToast(t("theme_imported")); + onClose(); + } catch (error) { + console.error(error); + showErrorToast(t("error_importing_theme")); + onClose(); + } + }; + + return ( + +
+ + + +
+
+ ); +}; From bf3905f19edcb17dcf85d19fbc34ea82482c8cc7 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sun, 16 Feb 2025 01:28:01 -0300 Subject: [PATCH 239/291] lint --- src/main/events/themes/import-theme.ts | 2 +- src/main/index.ts | 6 +- src/preload/index.ts | 7 +- src/renderer/src/app.tsx | 4 +- .../aparence/modals/import-theme-modal.tsx | 148 +++++++++--------- 5 files changed, 87 insertions(+), 80 deletions(-) diff --git a/src/main/events/themes/import-theme.ts b/src/main/events/themes/import-theme.ts index a5b06287..50ef940f 100644 --- a/src/main/events/themes/import-theme.ts +++ b/src/main/events/themes/import-theme.ts @@ -4,7 +4,7 @@ import { WindowManager } from "@main/services"; const importTheme = async ( _event: Electron.IpcMainInvokeEvent, theme: string, - author: string, + author: string ) => { WindowManager.mainWindow?.webContents.send("import-theme", theme, author); }; diff --git a/src/main/index.ts b/src/main/index.ts index b289a442..919e48c6 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -92,7 +92,11 @@ const handleDeepLinkPath = (uri?: string) => { const authorCode = url.searchParams.get("author"); if (themeName && authorCode) { - WindowManager.mainWindow?.webContents.send("import-theme", themeName, authorCode); + WindowManager.mainWindow?.webContents.send( + "import-theme", + themeName, + authorCode + ); } } } catch (error) { diff --git a/src/preload/index.ts b/src/preload/index.ts index a247bd17..16415b89 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -361,8 +361,11 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("getCustomThemeById", themeId), getActiveCustomTheme: () => ipcRenderer.invoke("getActiveCustomTheme"), onImportTheme: (cb: (theme: string, author: string) => void) => { - const listener = (_event: Electron.IpcRendererEvent, theme: string, author: string) => - cb(theme, author); + const listener = ( + _event: Electron.IpcRendererEvent, + theme: string, + author: string + ) => cb(theme, author); ipcRenderer.on("import-theme", listener); return () => ipcRenderer.removeListener("import-theme", listener); }, diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 0fd6370c..55a94e21 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -39,13 +39,13 @@ export interface AppProps { export function App() { const contentRef = useRef(null); const { updateLibrary, library } = useLibrary(); - const [isImportThemeModalVisible, setIsImportThemeModalVisible] = useState(false); + const [isImportThemeModalVisible, setIsImportThemeModalVisible] = + useState(false); const [importTheme, setImportTheme] = useState<{ theme: string; author: string; } | null>(null); - const { t } = useTranslation("app"); const { updateRepacks } = useRepacks(); diff --git a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx index 8431a647..65eea7c3 100644 --- a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx @@ -7,86 +7,86 @@ import { injectCustomCss, removeCustomCss } from "@renderer/helpers"; import { useToast } from "@renderer/hooks"; interface ImportThemeModalProps { - visible: boolean; - onClose: () => void; - onThemeImported: () => void; - themeName: string; - authorCode: string; + visible: boolean; + onClose: () => void; + onThemeImported: () => void; + themeName: string; + authorCode: string; } export const ImportThemeModal = ({ - visible, - onClose, - onThemeImported, - themeName, - authorCode, + visible, + onClose, + onThemeImported, + themeName, + authorCode, }: ImportThemeModalProps) => { - const { t } = useTranslation("settings"); - const { showSuccessToast, showErrorToast } = useToast(); + const { t } = useTranslation("settings"); + const { showSuccessToast, showErrorToast } = useToast(); - const handleImportTheme = async () => { - const theme: Theme = { - id: crypto.randomUUID(), - name: themeName, - isActive: false, - author: authorCode, - authorName: "spectre", - code: `https://hydrathemes.shop/themes/${themeName}.css`, - createdAt: new Date(), - updatedAt: new Date(), - }; - - try { - await window.electron.addCustomTheme(theme); - - const currentTheme = await window.electron.getCustomThemeById(theme.id); - - if (!currentTheme) return; - - const activeTheme = await window.electron.getActiveCustomTheme(); - - if (activeTheme) { - removeCustomCss(); - await window.electron.updateCustomTheme(activeTheme.id, { - ...activeTheme, - isActive: false, - }); - } - - if (currentTheme.code) { - injectCustomCss(currentTheme.code); - } - - await window.electron.updateCustomTheme(currentTheme.id, { - ...currentTheme, - isActive: true, - }); - onThemeImported(); - showSuccessToast(t("theme_imported")); - onClose(); - } catch (error) { - console.error(error); - showErrorToast(t("error_importing_theme")); - onClose(); - } + const handleImportTheme = async () => { + const theme: Theme = { + id: crypto.randomUUID(), + name: themeName, + isActive: false, + author: authorCode, + authorName: "spectre", + code: `https://hydrathemes.shop/themes/${themeName}.css`, + createdAt: new Date(), + updatedAt: new Date(), }; - return ( - -
- + try { + await window.electron.addCustomTheme(theme); - -
-
- ); + const currentTheme = await window.electron.getCustomThemeById(theme.id); + + if (!currentTheme) return; + + const activeTheme = await window.electron.getActiveCustomTheme(); + + if (activeTheme) { + removeCustomCss(); + await window.electron.updateCustomTheme(activeTheme.id, { + ...activeTheme, + isActive: false, + }); + } + + if (currentTheme.code) { + injectCustomCss(currentTheme.code); + } + + await window.electron.updateCustomTheme(currentTheme.id, { + ...currentTheme, + isActive: true, + }); + onThemeImported(); + showSuccessToast(t("theme_imported")); + onClose(); + } catch (error) { + console.error(error); + showErrorToast(t("error_importing_theme")); + onClose(); + } + }; + + return ( + +
+ + + +
+
+ ); }; From c36aff8f62a5d0cf69c3cbe6f8d8045cddc3219b Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 16 Feb 2025 04:28:55 +0000 Subject: [PATCH 240/291] fix: updating themes when installing --- src/renderer/src/declaration.d.ts | 1 + .../pages/settings/aparence/modals/add-theme-modal.tsx | 3 ++- .../src/pages/settings/aparence/settings-appearance.tsx | 8 ++++++++ src/renderer/src/pages/settings/settings-real-debrid.tsx | 6 +++--- src/types/theme.types.ts | 4 ++-- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 7141a619..fbb7fbe7 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -29,6 +29,7 @@ import type { LibraryGame, GameRunning, TorBoxUser, + Theme, } from "@types"; import type { AxiosProgressEvent } from "axios"; import type disk from "diskusage"; diff --git a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx index d78d5ddb..9df7e7a4 100644 --- a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx @@ -2,7 +2,6 @@ import { Modal } from "@renderer/components/modal/modal"; import { TextField } from "@renderer/components/text-field/text-field"; import { Button } from "@renderer/components/button/button"; import { useTranslation } from "react-i18next"; -import "./modals.scss"; import { useUserDetails } from "@renderer/hooks"; import { Theme } from "@types"; import { useForm } from "react-hook-form"; @@ -11,6 +10,8 @@ import * as yup from "yup"; import { yupResolver } from "@hookform/resolvers/yup"; import { useCallback } from "react"; +import "./modals.scss"; + interface AddThemeModalProps { visible: boolean; onClose: () => void; diff --git a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx index f45c3426..4a0ff317 100644 --- a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx +++ b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx @@ -15,6 +15,14 @@ export const SettingsAppearance = () => { loadThemes(); }, []); + useEffect(() => { + const unsubscribe = window.electron.onCssInjected(() => { + loadThemes(); + }); + + return () => unsubscribe(); + }, []); + return (
diff --git a/src/renderer/src/pages/settings/settings-real-debrid.tsx b/src/renderer/src/pages/settings/settings-real-debrid.tsx index 6fd1a6e2..d1c6bbb7 100644 --- a/src/renderer/src/pages/settings/settings-real-debrid.tsx +++ b/src/renderer/src/pages/settings/settings-real-debrid.tsx @@ -86,12 +86,12 @@ export function SettingsRealDebrid() { + onChange={() => { setForm((prev) => ({ ...prev, useRealDebrid: !form.useRealDebrid, - })) - } + })); + }} /> {form.useRealDebrid && ( diff --git a/src/types/theme.types.ts b/src/types/theme.types.ts index 259e5738..abba8fc1 100644 --- a/src/types/theme.types.ts +++ b/src/types/theme.types.ts @@ -1,8 +1,8 @@ export interface Theme { id: string; name: string; - author: string | undefined; - authorName: string | undefined; + author?: string; + authorName?: string; isActive: boolean; code: string; createdAt: Date; From 484fa863dc5eff4474f8b6a4543fcad30ecbac85 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 16 Feb 2025 05:18:08 +0000 Subject: [PATCH 241/291] fix: fixing stale state --- src/main/events/index.ts | 1 + src/main/events/themes/add-custom-theme.ts | 4 +- src/main/events/themes/deeplink.ts | 38 ------------------- .../events/themes/delete-all-custom-themes.ts | 5 +-- src/main/events/themes/delete-custom-theme.ts | 4 +- .../events/themes/get-active-custom-theme.ts | 7 ++-- .../events/themes/get-all-custom-themes.ts | 4 +- .../events/themes/get-custom-theme-by-id.ts | 4 +- src/main/events/themes/update-custom-theme.ts | 17 +++++++-- src/main/level/sublevels/index.ts | 1 + src/main/level/sublevels/themes.ts | 2 +- src/preload/index.ts | 6 ++- src/renderer/src/declaration.d.ts | 3 +- src/renderer/src/hooks/use-feature.ts | 11 +++++- .../aparence/components/theme-card.tsx | 15 ++------ .../aparence/modals/import-theme-modal.tsx | 10 +---- .../src/pages/theme-editor/theme-editor.tsx | 8 +--- 17 files changed, 50 insertions(+), 90 deletions(-) delete mode 100644 src/main/events/themes/deeplink.ts diff --git a/src/main/events/index.ts b/src/main/events/index.ts index f377cdba..d3f70c2d 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -88,6 +88,7 @@ import "./themes/get-active-custom-theme"; import "./themes/css-injector"; import "./themes/close-editor-window"; import "./themes/import-theme"; +import "./themes/toggle-custom-theme"; import { isPortableVersion } from "@main/helpers"; ipcMain.handle("ping", () => "pong"); diff --git a/src/main/events/themes/add-custom-theme.ts b/src/main/events/themes/add-custom-theme.ts index 8e9c4c98..95f526d9 100644 --- a/src/main/events/themes/add-custom-theme.ts +++ b/src/main/events/themes/add-custom-theme.ts @@ -1,12 +1,12 @@ import { Theme } from "@types"; -import { themes } from "@main/level/sublevels/themes"; import { registerEvent } from "../register-event"; +import { themesSublevel } from "@main/level"; const addCustomTheme = async ( _event: Electron.IpcMainInvokeEvent, theme: Theme ) => { - await themes.put(theme.id, theme); + await themesSublevel.put(theme.id, theme); }; registerEvent("addCustomTheme", addCustomTheme); diff --git a/src/main/events/themes/deeplink.ts b/src/main/events/themes/deeplink.ts deleted file mode 100644 index 47197a4f..00000000 --- a/src/main/events/themes/deeplink.ts +++ /dev/null @@ -1,38 +0,0 @@ -// import { themes } from "@main/level/sublevels/themes"; -// import { WindowManager } from "@main/services"; -// import { Theme } from "@types"; - -// export const handleDeepLinkTheme = async ( -// themeName: string, -// authorCode: string -// ) => { -// const theme: Theme = { -// id: crypto.randomUUID(), -// name: themeName, -// isActive: false, -// author: authorCode, -// authorName: "spectre", -// code: `https://hydrathemes.shop/themes/${themeName}.css`, -// createdAt: new Date(), -// updatedAt: new Date(), -// }; - -// await themes.put(theme.id, theme); - -// const allThemes = await themes.values().all(); -// const activeTheme = allThemes.find((theme: Theme) => theme.isActive); - -// if (activeTheme) { -// await themes.put(activeTheme.id, { -// ...activeTheme, -// isActive: false, -// }); -// } - -// WindowManager.mainWindow?.webContents.send("css-injected", theme.code); - -// await themes.put(theme.id, { -// ...theme, -// isActive: true, -// }); -// }; diff --git a/src/main/events/themes/delete-all-custom-themes.ts b/src/main/events/themes/delete-all-custom-themes.ts index a3c6018e..d7a42d39 100644 --- a/src/main/events/themes/delete-all-custom-themes.ts +++ b/src/main/events/themes/delete-all-custom-themes.ts @@ -1,9 +1,8 @@ -import { themes } from "@main/level/sublevels/themes"; +import { themesSublevel } from "@main/level"; import { registerEvent } from "../register-event"; const deleteAllCustomThemes = async (_event: Electron.IpcMainInvokeEvent) => { - console.log("sexo2"); - await themes.clear(); + await themesSublevel.clear(); }; registerEvent("deleteAllCustomThemes", deleteAllCustomThemes); diff --git a/src/main/events/themes/delete-custom-theme.ts b/src/main/events/themes/delete-custom-theme.ts index 604053e0..d47c43fb 100644 --- a/src/main/events/themes/delete-custom-theme.ts +++ b/src/main/events/themes/delete-custom-theme.ts @@ -1,11 +1,11 @@ -import { themes } from "@main/level/sublevels/themes"; +import { themesSublevel } from "@main/level"; import { registerEvent } from "../register-event"; const deleteCustomTheme = async ( _event: Electron.IpcMainInvokeEvent, themeId: string ) => { - await themes.del(themeId); + await themesSublevel.del(themeId); }; registerEvent("deleteCustomTheme", deleteCustomTheme); diff --git a/src/main/events/themes/get-active-custom-theme.ts b/src/main/events/themes/get-active-custom-theme.ts index a5018a35..b117f758 100644 --- a/src/main/events/themes/get-active-custom-theme.ts +++ b/src/main/events/themes/get-active-custom-theme.ts @@ -1,10 +1,9 @@ +import { themesSublevel } from "@main/level"; import { registerEvent } from "../register-event"; -import { themes } from "@main/level/sublevels/themes"; -import { Theme } from "@types"; const getActiveCustomTheme = async () => { - const allThemes = await themes.values().all(); - return allThemes.find((theme: Theme) => theme.isActive); + const allThemes = await themesSublevel.values().all(); + return allThemes.find((theme) => theme.isActive); }; registerEvent("getActiveCustomTheme", getActiveCustomTheme); diff --git a/src/main/events/themes/get-all-custom-themes.ts b/src/main/events/themes/get-all-custom-themes.ts index 87a9c02a..f59a87cd 100644 --- a/src/main/events/themes/get-all-custom-themes.ts +++ b/src/main/events/themes/get-all-custom-themes.ts @@ -1,8 +1,8 @@ -import { themes } from "@main/level/sublevels/themes"; +import { themesSublevel } from "@main/level"; import { registerEvent } from "../register-event"; const getAllCustomThemes = async (_event: Electron.IpcMainInvokeEvent) => { - return await themes.values().all(); + return themesSublevel.values().all(); }; registerEvent("getAllCustomThemes", getAllCustomThemes); diff --git a/src/main/events/themes/get-custom-theme-by-id.ts b/src/main/events/themes/get-custom-theme-by-id.ts index 78d2ccb3..4ec5dc03 100644 --- a/src/main/events/themes/get-custom-theme-by-id.ts +++ b/src/main/events/themes/get-custom-theme-by-id.ts @@ -1,11 +1,11 @@ -import { themes } from "@main/level/sublevels/themes"; +import { themesSublevel } from "@main/level"; import { registerEvent } from "../register-event"; const getCustomThemeById = async ( _event: Electron.IpcMainInvokeEvent, themeId: string ) => { - return await themes.get(themeId); + return themesSublevel.get(themeId); }; registerEvent("getCustomThemeById", getCustomThemeById); diff --git a/src/main/events/themes/update-custom-theme.ts b/src/main/events/themes/update-custom-theme.ts index 4773cfbf..38c97abe 100644 --- a/src/main/events/themes/update-custom-theme.ts +++ b/src/main/events/themes/update-custom-theme.ts @@ -1,13 +1,22 @@ -import { themes } from "@main/level/sublevels/themes"; +import { themesSublevel } from "@main/level"; import { registerEvent } from "../register-event"; -import { Theme } from "@types"; const updateCustomTheme = async ( _event: Electron.IpcMainInvokeEvent, themeId: string, - theme: Theme + code: string ) => { - await themes.put(themeId, theme); + const theme = await themesSublevel.get(themeId); + + if (!theme) { + throw new Error("Theme not found"); + } + + await themesSublevel.put(themeId, { + ...theme, + code, + updatedAt: new Date(), + }); }; registerEvent("updateCustomTheme", updateCustomTheme); diff --git a/src/main/level/sublevels/index.ts b/src/main/level/sublevels/index.ts index 3f0e840e..e63f0a3b 100644 --- a/src/main/level/sublevels/index.ts +++ b/src/main/level/sublevels/index.ts @@ -3,3 +3,4 @@ export * from "./games"; export * from "./game-shop-cache"; export * from "./game-achievements"; export * from "./keys"; +export * from "./themes"; diff --git a/src/main/level/sublevels/themes.ts b/src/main/level/sublevels/themes.ts index 3967c510..5e23468f 100644 --- a/src/main/level/sublevels/themes.ts +++ b/src/main/level/sublevels/themes.ts @@ -2,6 +2,6 @@ import type { Theme } from "@types"; import { db } from "../level"; import { levelKeys } from "./keys"; -export const themes = db.sublevel(levelKeys.themes, { +export const themesSublevel = db.sublevel(levelKeys.themes, { valueEncoding: "json", }); diff --git a/src/preload/index.ts b/src/preload/index.ts index 16415b89..34e3b8fd 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -355,11 +355,13 @@ contextBridge.exposeInMainWorld("electron", { deleteAllCustomThemes: () => ipcRenderer.invoke("deleteAllCustomThemes"), deleteCustomTheme: (themeId: string) => ipcRenderer.invoke("deleteCustomTheme", themeId), - updateCustomTheme: (themeId: string, theme: Theme) => - ipcRenderer.invoke("updateCustomTheme", themeId, theme), + updateCustomTheme: (themeId: string, code: string) => + ipcRenderer.invoke("updateCustomTheme", themeId, code), getCustomThemeById: (themeId: string) => ipcRenderer.invoke("getCustomThemeById", themeId), getActiveCustomTheme: () => ipcRenderer.invoke("getActiveCustomTheme"), + toggleCustomTheme: (themeId: string, isActive: boolean) => + ipcRenderer.invoke("toggleCustomTheme", themeId, isActive), onImportTheme: (cb: (theme: string, author: string) => void) => { const listener = ( _event: Electron.IpcRendererEvent, diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index e8a6bf3c..ab36b5c8 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -286,9 +286,10 @@ declare global { getAllCustomThemes: () => Promise; deleteAllCustomThemes: () => Promise; deleteCustomTheme: (themeId: string) => Promise; - updateCustomTheme: (themeId: string, theme: Theme) => Promise; + updateCustomTheme: (themeId: string, code: string) => Promise; getCustomThemeById: (themeId: string) => Promise; getActiveCustomTheme: () => Promise; + toggleCustomTheme: (themeId: string, isActive: boolean) => Promise; onImportTheme: ( cb: (theme: string, author: string) => void ) => () => Electron.IpcRenderer; diff --git a/src/renderer/src/hooks/use-feature.ts b/src/renderer/src/hooks/use-feature.ts index 0ba71fe2..65d0f694 100644 --- a/src/renderer/src/hooks/use-feature.ts +++ b/src/renderer/src/hooks/use-feature.ts @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useEffect, useState } from "react"; enum Feature { CheckDownloadWritePermission = "CHECK_DOWNLOAD_WRITE_PERMISSION", @@ -6,14 +6,21 @@ enum Feature { } export function useFeature() { + const [features, setFeatures] = useState(null); + useEffect(() => { window.electron.getFeatures().then((features) => { localStorage.setItem("features", JSON.stringify(features || [])); + setFeatures(features || []); }); }, []); const isFeatureEnabled = (feature: Feature) => { - const features = JSON.parse(localStorage.getItem("features") ?? "[]"); + if (!features) { + const features = JSON.parse(localStorage.getItem("features") ?? "[]"); + return features.includes(feature); + } + return features.includes(feature); }; diff --git a/src/renderer/src/pages/settings/aparence/components/theme-card.tsx b/src/renderer/src/pages/settings/aparence/components/theme-card.tsx index b9460ffc..d6226b7e 100644 --- a/src/renderer/src/pages/settings/aparence/components/theme-card.tsx +++ b/src/renderer/src/pages/settings/aparence/components/theme-card.tsx @@ -29,20 +29,14 @@ export const ThemeCard = ({ theme, onListUpdated }: ThemeCardProps) => { if (activeTheme) { removeCustomCss(); - await window.electron.updateCustomTheme(activeTheme.id, { - ...activeTheme, - isActive: false, - }); + await window.electron.toggleCustomTheme(activeTheme.id, false); } if (currentTheme.code) { injectCustomCss(currentTheme.code); } - await window.electron.updateCustomTheme(currentTheme.id, { - ...currentTheme, - isActive: true, - }); + await window.electron.toggleCustomTheme(currentTheme.id, true); onListUpdated(); } catch (error) { @@ -53,10 +47,7 @@ export const ThemeCard = ({ theme, onListUpdated }: ThemeCardProps) => { const handleUnsetTheme = async () => { try { removeCustomCss(); - await window.electron.updateCustomTheme(theme.id, { - ...theme, - isActive: false, - }); + await window.electron.toggleCustomTheme(theme.id, false); onListUpdated(); } catch (error) { diff --git a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx index 65eea7c3..eba51d6f 100644 --- a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx @@ -47,20 +47,14 @@ export const ImportThemeModal = ({ if (activeTheme) { removeCustomCss(); - await window.electron.updateCustomTheme(activeTheme.id, { - ...activeTheme, - isActive: false, - }); + await window.electron.toggleCustomTheme(activeTheme.id, false); } if (currentTheme.code) { injectCustomCss(currentTheme.code); } - await window.electron.updateCustomTheme(currentTheme.id, { - ...currentTheme, - isActive: true, - }); + await window.electron.toggleCustomTheme(currentTheme.id, true); onThemeImported(); showSuccessToast(t("theme_imported")); onClose(); diff --git a/src/renderer/src/pages/theme-editor/theme-editor.tsx b/src/renderer/src/pages/theme-editor/theme-editor.tsx index 9219f1d0..2bdfe6f2 100644 --- a/src/renderer/src/pages/theme-editor/theme-editor.tsx +++ b/src/renderer/src/pages/theme-editor/theme-editor.tsx @@ -31,13 +31,7 @@ export default function ThemeEditor() { const handleSave = useCallback(async () => { if (theme) { - const updatedTheme = { - ...theme, - code: code, - updatedAt: new Date(), - }; - - await window.electron.updateCustomTheme(theme.id, updatedTheme); + await window.electron.updateCustomTheme(theme.id, code); setHasUnsavedChanges(false); if (theme.isActive) { From d1dc27aef624e7c71299295a81e3276da84dbd7f Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 16 Feb 2025 05:27:16 +0000 Subject: [PATCH 242/291] fix: fixing stale state --- src/main/events/themes/toggle-custom-theme.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/events/themes/toggle-custom-theme.ts diff --git a/src/main/events/themes/toggle-custom-theme.ts b/src/main/events/themes/toggle-custom-theme.ts new file mode 100644 index 00000000..50440551 --- /dev/null +++ b/src/main/events/themes/toggle-custom-theme.ts @@ -0,0 +1,22 @@ +import { themesSublevel } from "@main/level"; +import { registerEvent } from "../register-event"; + +const toggleCustomTheme = async ( + _event: Electron.IpcMainInvokeEvent, + themeId: string, + isActive: boolean +) => { + const theme = await themesSublevel.get(themeId); + + if (!theme) { + throw new Error("Theme not found"); + } + + await themesSublevel.put(themeId, { + ...theme, + isActive, + updatedAt: new Date(), + }); +}; + +registerEvent("toggleCustomTheme", toggleCustomTheme); From ca75ad67218fcff72ef505d9d3d13d31d498920e Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sun, 16 Feb 2025 13:26:27 -0300 Subject: [PATCH 243/291] feat: improve theme import with dynamic author information --- .../settings/aparence/modals/import-theme-modal.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx index eba51d6f..b22809ff 100644 --- a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx @@ -5,7 +5,7 @@ import "./modals.scss"; import { Theme } from "@types"; import { injectCustomCss, removeCustomCss } from "@renderer/helpers"; import { useToast } from "@renderer/hooks"; - +import { UserProfile } from "@types"; interface ImportThemeModalProps { visible: boolean; onClose: () => void; @@ -24,13 +24,18 @@ export const ImportThemeModal = ({ const { t } = useTranslation("settings"); const { showSuccessToast, showErrorToast } = useToast(); + let author: UserProfile | null = null; + window.electron.getUser(authorCode).then((user) => { + author = user; + }); + const handleImportTheme = async () => { const theme: Theme = { id: crypto.randomUUID(), name: themeName, isActive: false, - author: authorCode, - authorName: "spectre", + author: author?.id, + authorName: author?.displayName, code: `https://hydrathemes.shop/themes/${themeName}.css`, createdAt: new Date(), updatedAt: new Date(), From ef28337729e15ef12a6c6cca07eb21e94978b827 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sun, 16 Feb 2025 15:17:11 -0300 Subject: [PATCH 244/291] feat: enhance theme import flow with redirect and modal --- src/main/index.ts | 15 ++++++---- .../src/context/settings/settings.context.tsx | 15 ++++++++-- .../settings/aparence/settings-appearance.tsx | 29 +++++++++++++++++++ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 919e48c6..1678d207 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -10,6 +10,7 @@ import { PythonRPC } from "./services/python-rpc"; import { Aria2 } from "./services/aria2"; import { db, levelKeys } from "./level"; import { loadState } from "./main"; +import { sleep } from "./helpers"; const { autoUpdater } = updater; @@ -92,11 +93,15 @@ const handleDeepLinkPath = (uri?: string) => { const authorCode = url.searchParams.get("author"); if (themeName && authorCode) { - WindowManager.mainWindow?.webContents.send( - "import-theme", - themeName, - authorCode - ); + WindowManager.redirect(`settings?theme=true`); + + sleep(1000).then(() => { + WindowManager.mainWindow?.webContents.send( + "import-theme", + themeName, + authorCode + ); + }); } } } catch (error) { diff --git a/src/renderer/src/context/settings/settings.context.tsx b/src/renderer/src/context/settings/settings.context.tsx index c1031a0c..b93efa55 100644 --- a/src/renderer/src/context/settings/settings.context.tsx +++ b/src/renderer/src/context/settings/settings.context.tsx @@ -37,22 +37,33 @@ export function SettingsContextProvider({ }: Readonly) { const dispatch = useAppDispatch(); const [sourceUrl, setSourceUrl] = useState(null); + const [appearanceUrl, setAppearanceUrl] = useState(null); const [currentCategoryIndex, setCurrentCategoryIndex] = useState(0); const [blockedUsers, setBlockedUsers] = useState([]); const [searchParams] = useSearchParams(); const defaultSourceUrl = searchParams.get("urls"); - + const defaultAppearanceUrl = searchParams.get("theme"); useEffect(() => { if (sourceUrl) setCurrentCategoryIndex(2); }, [sourceUrl]); - + useEffect(() => { if (defaultSourceUrl) { setSourceUrl(defaultSourceUrl); } }, [defaultSourceUrl]); + + useEffect(() => { + if (appearanceUrl) setCurrentCategoryIndex(3); + }, [appearanceUrl]); + + useEffect(() => { + if (defaultAppearanceUrl) { + setAppearanceUrl(defaultAppearanceUrl); + } + }, [defaultAppearanceUrl]); const fetchBlockedUsers = useCallback(async () => { const blockedUsers = await window.electron.getBlockedUsers(12, 0); diff --git a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx index 4a0ff317..7a11b46b 100644 --- a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx +++ b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx @@ -2,9 +2,16 @@ import { useEffect, useState } from "react"; import "./settings-appearance.scss"; import { ThemeActions, ThemeCard, ThemePlaceholder } from "./index"; import type { Theme } from "@types"; +import { ImportThemeModal } from "./modals/import-theme-modal"; export const SettingsAppearance = () => { const [themes, setThemes] = useState([]); + const [isImportThemeModalVisible, setIsImportThemeModalVisible] = + useState(false); + const [importTheme, setImportTheme] = useState<{ + theme: string; + author: string; + } | null>(null); const loadThemes = async () => { const themesList = await window.electron.getAllCustomThemes(); @@ -23,6 +30,15 @@ export const SettingsAppearance = () => { return () => unsubscribe(); }, []); + useEffect(() => { + const unsubscribe = window.electron.onImportTheme((theme, author) => { + setIsImportThemeModalVisible(true); + setImportTheme({ theme, author }); + }); + + return () => unsubscribe(); + }, []); + return (
@@ -46,6 +62,19 @@ export const SettingsAppearance = () => { )) )}
+ + {importTheme && ( + setIsImportThemeModalVisible(false)} + onThemeImported={() => { + setIsImportThemeModalVisible(false); + loadThemes(); + }} + themeName={importTheme.theme} + authorCode={importTheme.author} + /> + )}
); }; From 569c80cbf423a942521f21c1651b3e75a20c5955 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sun, 16 Feb 2025 16:10:02 -0300 Subject: [PATCH 245/291] refactor: simplify theme import handling and remove redundant code --- src/main/events/index.ts | 1 - src/main/events/themes/import-theme.ts | 12 ------ src/main/index.ts | 11 +---- src/preload/index.ts | 9 ----- src/renderer/src/app.tsx | 28 +------------ .../src/context/settings/settings.context.tsx | 40 ++++++++++++------- src/renderer/src/declaration.d.ts | 3 -- .../settings/aparence/settings-appearance.tsx | 20 ++++++---- src/renderer/src/pages/settings/settings.tsx | 14 ++++++- 9 files changed, 53 insertions(+), 85 deletions(-) delete mode 100644 src/main/events/themes/import-theme.ts diff --git a/src/main/events/index.ts b/src/main/events/index.ts index d3f70c2d..3dd9c298 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -87,7 +87,6 @@ import "./themes/get-custom-theme-by-id"; import "./themes/get-active-custom-theme"; import "./themes/css-injector"; import "./themes/close-editor-window"; -import "./themes/import-theme"; import "./themes/toggle-custom-theme"; import { isPortableVersion } from "@main/helpers"; diff --git a/src/main/events/themes/import-theme.ts b/src/main/events/themes/import-theme.ts deleted file mode 100644 index 50ef940f..00000000 --- a/src/main/events/themes/import-theme.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { registerEvent } from "../register-event"; -import { WindowManager } from "@main/services"; - -const importTheme = async ( - _event: Electron.IpcMainInvokeEvent, - theme: string, - author: string -) => { - WindowManager.mainWindow?.webContents.send("import-theme", theme, author); -}; - -registerEvent("importTheme", importTheme); diff --git a/src/main/index.ts b/src/main/index.ts index 1678d207..7b4ee618 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -10,7 +10,6 @@ import { PythonRPC } from "./services/python-rpc"; import { Aria2 } from "./services/aria2"; import { db, levelKeys } from "./level"; import { loadState } from "./main"; -import { sleep } from "./helpers"; const { autoUpdater } = updater; @@ -93,15 +92,7 @@ const handleDeepLinkPath = (uri?: string) => { const authorCode = url.searchParams.get("author"); if (themeName && authorCode) { - WindowManager.redirect(`settings?theme=true`); - - sleep(1000).then(() => { - WindowManager.mainWindow?.webContents.send( - "import-theme", - themeName, - authorCode - ); - }); + WindowManager.redirect(`settings?theme=${themeName}&author=${authorCode}`); } } } catch (error) { diff --git a/src/preload/index.ts b/src/preload/index.ts index 34e3b8fd..a7b7b753 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -362,15 +362,6 @@ contextBridge.exposeInMainWorld("electron", { getActiveCustomTheme: () => ipcRenderer.invoke("getActiveCustomTheme"), toggleCustomTheme: (themeId: string, isActive: boolean) => ipcRenderer.invoke("toggleCustomTheme", themeId, isActive), - onImportTheme: (cb: (theme: string, author: string) => void) => { - const listener = ( - _event: Electron.IpcRendererEvent, - theme: string, - author: string - ) => cb(theme, author); - ipcRenderer.on("import-theme", listener); - return () => ipcRenderer.removeListener("import-theme", listener); - }, /* Editor */ openEditorWindow: (themeId: string) => diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 55a94e21..daa93a6b 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef } from "react"; import achievementSound from "@renderer/assets/audio/achievement.wav"; import { Sidebar, BottomPanel, Header, Toast } from "@renderer/components"; @@ -30,7 +30,6 @@ import { HydraCloudModal } from "./pages/shared-modals/hydra-cloud/hydra-cloud-m import { injectCustomCss } from "./helpers"; import "./app.scss"; -import { ImportThemeModal } from "./pages/settings/aparence/modals/import-theme-modal"; export interface AppProps { children: React.ReactNode; @@ -39,12 +38,6 @@ export interface AppProps { export function App() { const contentRef = useRef(null); const { updateLibrary, library } = useLibrary(); - const [isImportThemeModalVisible, setIsImportThemeModalVisible] = - useState(false); - const [importTheme, setImportTheme] = useState<{ - theme: string; - author: string; - } | null>(null); const { t } = useTranslation("app"); @@ -278,15 +271,6 @@ export function App() { return () => unsubscribe(); }, []); - useEffect(() => { - const unsubscribe = window.electron.onImportTheme((theme, author) => { - setImportTheme({ theme, author }); - setIsImportThemeModalVisible(true); - }); - - return () => unsubscribe(); - }, []); - const handleToastClose = useCallback(() => { dispatch(closeToast()); }, [dispatch]); @@ -328,16 +312,6 @@ export function App() { /> )} - {importTheme && ( - setIsImportThemeModalVisible(false)} - onThemeImported={() => setIsImportThemeModalVisible(false)} - themeName={importTheme.theme} - authorCode={importTheme.author} - /> - )} -
diff --git a/src/renderer/src/context/settings/settings.context.tsx b/src/renderer/src/context/settings/settings.context.tsx index b93efa55..ca5af9a4 100644 --- a/src/renderer/src/context/settings/settings.context.tsx +++ b/src/renderer/src/context/settings/settings.context.tsx @@ -13,16 +13,20 @@ export interface SettingsContext { currentCategoryIndex: number; blockedUsers: UserBlocks["blocks"]; fetchBlockedUsers: () => Promise; + appearanceTheme: string | null; + appearanceAuthor: string | null; } export const settingsContext = createContext({ - updateUserPreferences: async () => {}, - setCurrentCategoryIndex: () => {}, - clearSourceUrl: () => {}, + updateUserPreferences: async () => { }, + setCurrentCategoryIndex: () => { }, + clearSourceUrl: () => { }, sourceUrl: null, currentCategoryIndex: 0, blockedUsers: [], - fetchBlockedUsers: async () => {}, + fetchBlockedUsers: async () => { }, + appearanceTheme: null, + appearanceAuthor: null, }); const { Provider } = settingsContext; @@ -37,33 +41,39 @@ export function SettingsContextProvider({ }: Readonly) { const dispatch = useAppDispatch(); const [sourceUrl, setSourceUrl] = useState(null); - const [appearanceUrl, setAppearanceUrl] = useState(null); + const [appearanceTheme, setAppearanceTheme] = useState(null); + const [appearanceAuthor, setAppearanceAuthor] = useState( + null + ); const [currentCategoryIndex, setCurrentCategoryIndex] = useState(0); const [blockedUsers, setBlockedUsers] = useState([]); const [searchParams] = useSearchParams(); const defaultSourceUrl = searchParams.get("urls"); - const defaultAppearanceUrl = searchParams.get("theme"); + const defaultAppearanceTheme = searchParams.get("theme"); + const defaultAppearanceAuthor = searchParams.get("author"); + useEffect(() => { if (sourceUrl) setCurrentCategoryIndex(2); }, [sourceUrl]); - + useEffect(() => { if (defaultSourceUrl) { setSourceUrl(defaultSourceUrl); } }, [defaultSourceUrl]); - + useEffect(() => { - if (appearanceUrl) setCurrentCategoryIndex(3); - }, [appearanceUrl]); - + if (appearanceTheme) setCurrentCategoryIndex(3); + }, [appearanceTheme]); + useEffect(() => { - if (defaultAppearanceUrl) { - setAppearanceUrl(defaultAppearanceUrl); + if (defaultAppearanceTheme && defaultAppearanceAuthor) { + setAppearanceTheme(defaultAppearanceTheme); + setAppearanceAuthor(defaultAppearanceAuthor); } - }, [defaultAppearanceUrl]); + }, [defaultAppearanceTheme, defaultAppearanceAuthor]); const fetchBlockedUsers = useCallback(async () => { const blockedUsers = await window.electron.getBlockedUsers(12, 0); @@ -93,6 +103,8 @@ export function SettingsContextProvider({ currentCategoryIndex, sourceUrl, blockedUsers, + appearanceTheme, + appearanceAuthor, }} > {children} diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index ab36b5c8..2deffb05 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -290,9 +290,6 @@ declare global { getCustomThemeById: (themeId: string) => Promise; getActiveCustomTheme: () => Promise; toggleCustomTheme: (themeId: string, isActive: boolean) => Promise; - onImportTheme: ( - cb: (theme: string, author: string) => void - ) => () => Electron.IpcRenderer; /* Editor */ openEditorWindow: (themeId: string) => Promise; diff --git a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx index 7a11b46b..4da0193f 100644 --- a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx +++ b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx @@ -4,7 +4,12 @@ import { ThemeActions, ThemeCard, ThemePlaceholder } from "./index"; import type { Theme } from "@types"; import { ImportThemeModal } from "./modals/import-theme-modal"; -export const SettingsAppearance = () => { +interface SettingsAppearanceProps { + appearanceTheme: string | null; + appearanceAuthor: string | null; +} + +export const SettingsAppearance = ({ appearanceTheme, appearanceAuthor }: SettingsAppearanceProps) => { const [themes, setThemes] = useState([]); const [isImportThemeModalVisible, setIsImportThemeModalVisible] = useState(false); @@ -31,13 +36,14 @@ export const SettingsAppearance = () => { }, []); useEffect(() => { - const unsubscribe = window.electron.onImportTheme((theme, author) => { + if (appearanceTheme && appearanceAuthor) { setIsImportThemeModalVisible(true); - setImportTheme({ theme, author }); - }); - - return () => unsubscribe(); - }, []); + setImportTheme({ + theme: appearanceTheme, + author: appearanceAuthor + }); + } + }, [appearanceTheme, appearanceAuthor]); return (
diff --git a/src/renderer/src/pages/settings/settings.tsx b/src/renderer/src/pages/settings/settings.tsx index f28cf6e5..4fa7821c 100644 --- a/src/renderer/src/pages/settings/settings.tsx +++ b/src/renderer/src/pages/settings/settings.tsx @@ -61,7 +61,12 @@ export default function Settings() { return ( - {({ currentCategoryIndex, setCurrentCategoryIndex }) => { + {({ + currentCategoryIndex, + setCurrentCategoryIndex, + appearanceTheme, + appearanceAuthor, + }) => { const renderCategory = () => { if (currentCategoryIndex === 0) { return ; @@ -76,7 +81,12 @@ export default function Settings() { } if (currentCategoryIndex === 3) { - return ; + return ( + + ); } if (currentCategoryIndex === 4) { From 4cfecf849310e061a176e1e8191bc06ff48491b2 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sun, 16 Feb 2025 16:10:14 -0300 Subject: [PATCH 246/291] lint --- src/main/index.ts | 4 +++- .../src/context/settings/settings.context.tsx | 12 +++++------- .../pages/settings/aparence/settings-appearance.tsx | 7 +++++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 7b4ee618..3665c2b7 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -92,7 +92,9 @@ const handleDeepLinkPath = (uri?: string) => { const authorCode = url.searchParams.get("author"); if (themeName && authorCode) { - WindowManager.redirect(`settings?theme=${themeName}&author=${authorCode}`); + WindowManager.redirect( + `settings?theme=${themeName}&author=${authorCode}` + ); } } } catch (error) { diff --git a/src/renderer/src/context/settings/settings.context.tsx b/src/renderer/src/context/settings/settings.context.tsx index ca5af9a4..95c8623f 100644 --- a/src/renderer/src/context/settings/settings.context.tsx +++ b/src/renderer/src/context/settings/settings.context.tsx @@ -18,13 +18,13 @@ export interface SettingsContext { } export const settingsContext = createContext({ - updateUserPreferences: async () => { }, - setCurrentCategoryIndex: () => { }, - clearSourceUrl: () => { }, + updateUserPreferences: async () => {}, + setCurrentCategoryIndex: () => {}, + clearSourceUrl: () => {}, sourceUrl: null, currentCategoryIndex: 0, blockedUsers: [], - fetchBlockedUsers: async () => { }, + fetchBlockedUsers: async () => {}, appearanceTheme: null, appearanceAuthor: null, }); @@ -42,9 +42,7 @@ export function SettingsContextProvider({ const dispatch = useAppDispatch(); const [sourceUrl, setSourceUrl] = useState(null); const [appearanceTheme, setAppearanceTheme] = useState(null); - const [appearanceAuthor, setAppearanceAuthor] = useState( - null - ); + const [appearanceAuthor, setAppearanceAuthor] = useState(null); const [currentCategoryIndex, setCurrentCategoryIndex] = useState(0); const [blockedUsers, setBlockedUsers] = useState([]); diff --git a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx index 4da0193f..d14b4fe7 100644 --- a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx +++ b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx @@ -9,7 +9,10 @@ interface SettingsAppearanceProps { appearanceAuthor: string | null; } -export const SettingsAppearance = ({ appearanceTheme, appearanceAuthor }: SettingsAppearanceProps) => { +export const SettingsAppearance = ({ + appearanceTheme, + appearanceAuthor, +}: SettingsAppearanceProps) => { const [themes, setThemes] = useState([]); const [isImportThemeModalVisible, setIsImportThemeModalVisible] = useState(false); @@ -40,7 +43,7 @@ export const SettingsAppearance = ({ appearanceTheme, appearanceAuthor }: Settin setIsImportThemeModalVisible(true); setImportTheme({ theme: appearanceTheme, - author: appearanceAuthor + author: appearanceAuthor, }); } }, [appearanceTheme, appearanceAuthor]); From e3a4f12140b787e8ab50dac54608970fc45391c3 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 16 Feb 2025 19:13:12 +0000 Subject: [PATCH 247/291] feat: adding deeplink for profile --- src/main/index.ts | 6 ++++++ .../pages/profile/report-profile/report-profile.tsx | 12 +++++------- .../settings/aparence/modals/add-theme-modal.tsx | 11 +++++------ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 919e48c6..1ff70ebd 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -85,6 +85,12 @@ const handleDeepLinkPath = (uri?: string) => { if (url.host === "install-source") { WindowManager.redirect(`settings${url.search}`); + return; + } + + if (url.host === "profile") { + WindowManager.redirect(`profile${url.search}`); + return; } if (url.host === "install-theme") { diff --git a/src/renderer/src/pages/profile/report-profile/report-profile.tsx b/src/renderer/src/pages/profile/report-profile/report-profile.tsx index 04afea51..40084aba 100644 --- a/src/renderer/src/pages/profile/report-profile/report-profile.tsx +++ b/src/renderer/src/pages/profile/report-profile/report-profile.tsx @@ -74,7 +74,10 @@ export function ReportProfile() { title={t("report_profile")} clickOutsideToClose={false} > -
+ - + diff --git a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx index 9df7e7a4..2427d6ae 100644 --- a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx @@ -74,7 +74,10 @@ export function AddThemeModal({ description={t("create_theme_modal_description")} onClose={onClose} > -
+ - From a13e991d2cd7477bf1c0081598e8bf7c4c3fcd5f Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sun, 16 Feb 2025 16:24:57 -0300 Subject: [PATCH 248/291] feat: add theme import translations and modal improvements --- src/locales/pt-BR/translation.json | 10 ++++++---- .../settings/aparence/modals/import-theme-modal.tsx | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index a8037b2d..0cefd188 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -294,9 +294,7 @@ "subscription_renew_cancelled": "A renovação automática está desativada", "subscription_renews_on": "Sua assinatura renova dia {{date}}", "bill_sent_until": "Sua próxima cobrança será enviada até esse dia", - "no_themes": "Parace que você ainda não tem nenhum tema. Não se preocupe, clique aqui para criar sua primeira obra de arte.", - "editor_tab_code": "Código", - "editor_tab_info": "Info", + "no_themes": "Parece que você ainda não tem nenhum tema. Não se preocupe, clique aqui para criar sua primeira obra de arte.", "editor_tab_save": "Salvar", "web_store": "Loja de temas", "clear_themes": "Limpar", @@ -318,7 +316,11 @@ "torbox_description": "TorBox é o seu serviço de seedbox premium que rivaliza até com os melhores servidores do mercado.", "torbox_account_linked": "Conta do TorBox vinculada", "real_debrid_account_linked": "Conta Real-Debrid associada", - "name_min_length": "O nome do tema deve ter pelo menos 3 caracteres" + "name_min_length": "O nome do tema deve ter pelo menos 3 caracteres", + "import_theme": "Importar tema", + "import_theme_description": "Você irá importar {{theme}} da loja de temas", + "error_importing_theme": "Erro ao importar tema", + "theme_imported": "Tema importado com sucesso" }, "notifications": { "download_complete": "Download concluído", diff --git a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx index b22809ff..f2039673 100644 --- a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx @@ -2,10 +2,10 @@ import { Button } from "@renderer/components/button/button"; import { Modal } from "@renderer/components/modal/modal"; import { useTranslation } from "react-i18next"; import "./modals.scss"; -import { Theme } from "@types"; +import { Theme, UserProfile } from "@types"; import { injectCustomCss, removeCustomCss } from "@renderer/helpers"; import { useToast } from "@renderer/hooks"; -import { UserProfile } from "@types"; + interface ImportThemeModalProps { visible: boolean; onClose: () => void; @@ -74,7 +74,7 @@ export const ImportThemeModal = ({
From cd48acc7e682ef2f189c412e08ba4422689ba4ab Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 16 Feb 2025 19:28:28 +0000 Subject: [PATCH 249/291] feat: adding hydra shop constant --- src/renderer/src/constants.ts | 2 ++ .../src/pages/settings/aparence/components/theme-actions.tsx | 3 +-- .../src/pages/settings/aparence/components/theme-card.tsx | 3 ++- .../src/pages/settings/aparence/modals/import-theme-modal.tsx | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/constants.ts b/src/renderer/src/constants.ts index 83293cb6..9db659dc 100644 --- a/src/renderer/src/constants.ts +++ b/src/renderer/src/constants.ts @@ -14,3 +14,5 @@ export const DOWNLOADER_NAME = { }; export const MAX_MINUTES_TO_SHOW_IN_PLAYTIME = 120; + +export const WEB_STORE_URL = "https://hydrathemes.shop"; diff --git a/src/renderer/src/pages/settings/aparence/components/theme-actions.tsx b/src/renderer/src/pages/settings/aparence/components/theme-actions.tsx index 3f98d8cc..3be3cb51 100644 --- a/src/renderer/src/pages/settings/aparence/components/theme-actions.tsx +++ b/src/renderer/src/pages/settings/aparence/components/theme-actions.tsx @@ -4,8 +4,7 @@ import { useTranslation } from "react-i18next"; import { AddThemeModal, DeleteAllThemesModal } from "../index"; import "./theme-actions.scss"; import { useState } from "react"; - -export const WEB_STORE_URL = "https://hydrathemes.shop"; +import { WEB_STORE_URL } from "@renderer/constants"; interface ThemeActionsProps { onListUpdated: () => void; diff --git a/src/renderer/src/pages/settings/aparence/components/theme-card.tsx b/src/renderer/src/pages/settings/aparence/components/theme-card.tsx index d6226b7e..35c18d85 100644 --- a/src/renderer/src/pages/settings/aparence/components/theme-card.tsx +++ b/src/renderer/src/pages/settings/aparence/components/theme-card.tsx @@ -7,6 +7,7 @@ import "./theme-card.scss"; import { useState } from "react"; import { DeleteThemeModal } from "../modals/delete-theme-modal"; import { injectCustomCss, removeCustomCss } from "@renderer/helpers"; +import { WEB_STORE_URL } from "@renderer/constants"; interface ThemeCardProps { theme: Theme; @@ -103,7 +104,7 @@ export const ThemeCard = ({ theme, onListUpdated }: ThemeCardProps) => {
diff --git a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx index 91f27698..550b8241 100644 --- a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx @@ -6,6 +6,7 @@ import { Theme, UserProfile } from "@types"; import { injectCustomCss, removeCustomCss } from "@renderer/helpers"; import { useToast } from "@renderer/hooks"; import { THEME_WEB_STORE_URL } from "@renderer/constants"; +import { logger } from "@renderer/logger"; interface ImportThemeModalProps { visible: boolean; @@ -65,7 +66,7 @@ export const ImportThemeModal = ({ showSuccessToast(t("theme_imported")); onClose(); } catch (error) { - console.error(error); + logger.error(error); showErrorToast(t("error_importing_theme")); onClose(); } From b74e093ddf323e87b4d79107777dc35f63a7ab85 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sun, 16 Feb 2025 17:05:55 -0300 Subject: [PATCH 252/291] fix: ensure objectId exists before removing game from favorites --- .../src/pages/game-details/hero/hero-panel-actions.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx index 0646b0f4..eee40df8 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx +++ b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx @@ -61,9 +61,9 @@ export function HeroPanelActions() { setToggleLibraryGameDisabled(true); try { - if (game?.favorite) { + if (game?.favorite && objectId) { await window.electron - .removeGameFromFavorites(shop, objectId!) + .removeGameFromFavorites(shop, objectId) .then(() => { showSuccessToast(t("game_removed_from_favorites")); }); From 002f8144fdbf90d3a738478998b6cbe0be31aa59 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 16 Feb 2025 20:06:11 +0000 Subject: [PATCH 253/291] feat: adding lower case to theme --- src/renderer/src/pages/settings/add-download-source-modal.tsx | 2 +- .../src/pages/settings/aparence/modals/import-theme-modal.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/settings/add-download-source-modal.tsx b/src/renderer/src/pages/settings/add-download-source-modal.tsx index 2a9b5dd6..948dc721 100644 --- a/src/renderer/src/pages/settings/add-download-source-modal.tsx +++ b/src/renderer/src/pages/settings/add-download-source-modal.tsx @@ -26,7 +26,7 @@ export function AddDownloadSourceModal({ visible, onClose, onAddDownloadSource, -}: AddDownloadSourceModalProps) { +}: Readonly) { const [url, setUrl] = useState(""); const [isLoading, setIsLoading] = useState(false); diff --git a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx index 550b8241..7f40f8f1 100644 --- a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx @@ -38,7 +38,7 @@ export const ImportThemeModal = ({ isActive: false, author: author?.id, authorName: author?.displayName, - code: `${THEME_WEB_STORE_URL}/themes/${themeName}/theme.css`, + code: `${THEME_WEB_STORE_URL}/themes/${themeName.toLowerCase()}/theme.css`, createdAt: new Date(), updatedAt: new Date(), }; From a372afc92c67645f978822dee5cfabe48f06d111 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sun, 16 Feb 2025 17:07:39 -0300 Subject: [PATCH 254/291] fix: prevent adding game to favorites without objectId --- .../src/pages/game-details/hero/hero-panel-actions.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx index eee40df8..1ba24705 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx +++ b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx @@ -68,7 +68,9 @@ export function HeroPanelActions() { showSuccessToast(t("game_removed_from_favorites")); }); } else { - await window.electron.addGameToFavorites(shop, objectId!).then(() => { + if (!objectId) return; + + await window.electron.addGameToFavorites(shop, objectId).then(() => { showSuccessToast(t("game_added_to_favorites")); }); } From 043062eda88dd397129ee345cf93660f19d80739 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sun, 16 Feb 2025 17:08:23 -0300 Subject: [PATCH 255/291] remove whitespace --- src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx index 1ba24705..a3b75d2e 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx +++ b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx @@ -69,7 +69,7 @@ export function HeroPanelActions() { }); } else { if (!objectId) return; - + await window.electron.addGameToFavorites(shop, objectId).then(() => { showSuccessToast(t("game_added_to_favorites")); }); From 401e61a6ec2665957ab35c53a0f44596d6d1f998 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 16 Feb 2025 20:19:25 +0000 Subject: [PATCH 256/291] fix: fixing toast bottom --- src/renderer/src/components/toast/toast.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/components/toast/toast.scss b/src/renderer/src/components/toast/toast.scss index e5320231..86449649 100644 --- a/src/renderer/src/components/toast/toast.scss +++ b/src/renderer/src/components/toast/toast.scss @@ -7,8 +7,9 @@ background-color: globals.$dark-background-color; border-radius: 4px; border: solid 1px globals.$border-color; - right: 16px; - bottom: 26px + globals.$spacing-unit; + right: calc(globals.$spacing-unit * 2); + // 28px is the height of the bottom panel + bottom: calc(28px + globals.$spacing-unit * 2); overflow: hidden; display: flex; flex-direction: column; From b999da31d442511099ef982565800c6f52c63cf3 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 16 Feb 2025 20:32:18 +0000 Subject: [PATCH 257/291] fix: fixing update of inactive theme --- src/main/events/index.ts | 1 - src/main/events/themes/css-injector.ts | 11 ------ src/main/events/themes/update-custom-theme.ts | 5 +++ src/preload/index.ts | 1 - src/renderer/src/declaration.d.ts | 1 - .../aparence/modals/add-theme-modal.tsx | 34 ++++++++++++++++++- .../src/pages/theme-editor/theme-editor.tsx | 4 --- 7 files changed, 38 insertions(+), 19 deletions(-) delete mode 100644 src/main/events/themes/css-injector.ts diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 3dd9c298..572cba0f 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -85,7 +85,6 @@ import "./themes/update-custom-theme"; import "./themes/open-editor-window"; import "./themes/get-custom-theme-by-id"; import "./themes/get-active-custom-theme"; -import "./themes/css-injector"; import "./themes/close-editor-window"; import "./themes/toggle-custom-theme"; import { isPortableVersion } from "@main/helpers"; diff --git a/src/main/events/themes/css-injector.ts b/src/main/events/themes/css-injector.ts deleted file mode 100644 index facee221..00000000 --- a/src/main/events/themes/css-injector.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { registerEvent } from "../register-event"; -import { WindowManager } from "@main/services"; - -const injectCSS = async ( - _event: Electron.IpcMainInvokeEvent, - cssString: string -) => { - WindowManager.mainWindow?.webContents.send("css-injected", cssString); -}; - -registerEvent("injectCSS", injectCSS); diff --git a/src/main/events/themes/update-custom-theme.ts b/src/main/events/themes/update-custom-theme.ts index 38c97abe..b9a8e048 100644 --- a/src/main/events/themes/update-custom-theme.ts +++ b/src/main/events/themes/update-custom-theme.ts @@ -1,5 +1,6 @@ import { themesSublevel } from "@main/level"; import { registerEvent } from "../register-event"; +import { WindowManager } from "@main/services"; const updateCustomTheme = async ( _event: Electron.IpcMainInvokeEvent, @@ -17,6 +18,10 @@ const updateCustomTheme = async ( code, updatedAt: new Date(), }); + + if (theme.isActive) { + WindowManager.mainWindow?.webContents.send("css-injected", code); + } }; registerEvent("updateCustomTheme", updateCustomTheme); diff --git a/src/preload/index.ts b/src/preload/index.ts index a7b7b753..5b3498ac 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -366,7 +366,6 @@ contextBridge.exposeInMainWorld("electron", { /* Editor */ openEditorWindow: (themeId: string) => ipcRenderer.invoke("openEditorWindow", themeId), - injectCSS: (cssString: string) => ipcRenderer.invoke("injectCSS", cssString), onCssInjected: (cb: (cssString: string) => void) => { const listener = (_event: Electron.IpcRendererEvent, cssString: string) => cb(cssString); diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 2deffb05..1b588d5c 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -293,7 +293,6 @@ declare global { /* Editor */ openEditorWindow: (themeId: string) => Promise; - injectCSS: (cssString: string) => Promise; onCssInjected: ( cb: (cssString: string) => void ) => () => Electron.IpcRenderer; diff --git a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx index f025b71c..bece2bac 100644 --- a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx @@ -22,6 +22,38 @@ interface FormValues { name: string; } +const DEFAULT_THEME_CODE = ` + /* + Here you can edit CSS for your theme and apply it on Hydra. + There are a few classes already in place, you can use them to style the launcher. + + If you want to learn more about how to run Hydra in dev mode (which will allow you to inspect the DOM and view the classes) + or how to publish your theme in the theme store, you can check the docs: + https://docs.hydralauncher.gg/ + + Happy hacking! + */ + + /* Header */ + .header {} + + /* Sidebar */ + .sidebar {} + + /* Main content */ + .container__content {} + + /* Bottom panel */ + .bottom-panel {} + + /* Toast */ + .toast {} + + /* Button */ + .button {} + +`; + export function AddThemeModal({ visible, onClose, @@ -54,7 +86,7 @@ export function AddThemeModal({ isActive: false, author: userDetails?.id, authorName: userDetails?.username, - code: "/* Your theme goes here */", + code: DEFAULT_THEME_CODE, createdAt: new Date(), updatedAt: new Date(), }; diff --git a/src/renderer/src/pages/theme-editor/theme-editor.tsx b/src/renderer/src/pages/theme-editor/theme-editor.tsx index 2bdfe6f2..0059edc9 100644 --- a/src/renderer/src/pages/theme-editor/theme-editor.tsx +++ b/src/renderer/src/pages/theme-editor/theme-editor.tsx @@ -33,10 +33,6 @@ export default function ThemeEditor() { if (theme) { await window.electron.updateCustomTheme(theme.id, code); setHasUnsavedChanges(false); - - if (theme.isActive) { - window.electron.injectCSS(code); - } } }, [code, theme]); From 7064da8b05f9326022cc70fb038114390aeaac48 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sun, 16 Feb 2025 17:40:51 -0300 Subject: [PATCH 258/291] feat: update theme installation with author details --- src/main/index.ts | 7 +++-- .../src/context/settings/settings.context.tsx | 30 ++++++++++++++----- .../aparence/modals/import-theme-modal.tsx | 17 +++++------ .../settings/aparence/settings-appearance.tsx | 19 +++++++----- src/renderer/src/pages/settings/settings.tsx | 12 ++++++-- 5 files changed, 54 insertions(+), 31 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 9e91c349..b56320b4 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -100,11 +100,12 @@ const handleDeepLinkPath = (uri?: string) => { if (url.host === "install-theme") { const themeName = url.searchParams.get("theme"); - const authorCode = url.searchParams.get("author"); + const authorId = url.searchParams.get("authorId"); + const authorName = url.searchParams.get("authorName"); - if (themeName && authorCode) { + if (themeName && authorId && authorName) { WindowManager.redirect( - `settings?theme=${themeName}&author=${authorCode}` + `settings?theme=${themeName}&authorId=${authorId}&authorName=${authorName}` ); } } diff --git a/src/renderer/src/context/settings/settings.context.tsx b/src/renderer/src/context/settings/settings.context.tsx index 95c8623f..8aa22f14 100644 --- a/src/renderer/src/context/settings/settings.context.tsx +++ b/src/renderer/src/context/settings/settings.context.tsx @@ -14,7 +14,8 @@ export interface SettingsContext { blockedUsers: UserBlocks["blocks"]; fetchBlockedUsers: () => Promise; appearanceTheme: string | null; - appearanceAuthor: string | null; + appearanceAuthorId: string | null; + appearanceAuthorName: string | null; } export const settingsContext = createContext({ @@ -26,7 +27,8 @@ export const settingsContext = createContext({ blockedUsers: [], fetchBlockedUsers: async () => {}, appearanceTheme: null, - appearanceAuthor: null, + appearanceAuthorId: null, + appearanceAuthorName: null, }); const { Provider } = settingsContext; @@ -42,7 +44,12 @@ export function SettingsContextProvider({ const dispatch = useAppDispatch(); const [sourceUrl, setSourceUrl] = useState(null); const [appearanceTheme, setAppearanceTheme] = useState(null); - const [appearanceAuthor, setAppearanceAuthor] = useState(null); + const [appearanceAuthorId, setAppearanceAuthorId] = useState( + null + ); + const [appearanceAuthorName, setAppearanceAuthorName] = useState( + null + ); const [currentCategoryIndex, setCurrentCategoryIndex] = useState(0); const [blockedUsers, setBlockedUsers] = useState([]); @@ -50,7 +57,8 @@ export function SettingsContextProvider({ const [searchParams] = useSearchParams(); const defaultSourceUrl = searchParams.get("urls"); const defaultAppearanceTheme = searchParams.get("theme"); - const defaultAppearanceAuthor = searchParams.get("author"); + const defaultAppearanceAuthorId = searchParams.get("authorId"); + const defaultAppearanceAuthorName = searchParams.get("authorName"); useEffect(() => { if (sourceUrl) setCurrentCategoryIndex(2); @@ -67,11 +75,16 @@ export function SettingsContextProvider({ }, [appearanceTheme]); useEffect(() => { - if (defaultAppearanceTheme && defaultAppearanceAuthor) { + if (defaultAppearanceTheme && defaultAppearanceAuthorId && defaultAppearanceAuthorName) { setAppearanceTheme(defaultAppearanceTheme); - setAppearanceAuthor(defaultAppearanceAuthor); + setAppearanceAuthorId(defaultAppearanceAuthorId); + setAppearanceAuthorName(defaultAppearanceAuthorName); } - }, [defaultAppearanceTheme, defaultAppearanceAuthor]); + }, [ + defaultAppearanceTheme, + defaultAppearanceAuthorId, + defaultAppearanceAuthorName, + ]); const fetchBlockedUsers = useCallback(async () => { const blockedUsers = await window.electron.getBlockedUsers(12, 0); @@ -102,7 +115,8 @@ export function SettingsContextProvider({ sourceUrl, blockedUsers, appearanceTheme, - appearanceAuthor, + appearanceAuthorId, + appearanceAuthorName, }} > {children} diff --git a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx index 7f40f8f1..db4abe8c 100644 --- a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx @@ -2,7 +2,7 @@ import { Button } from "@renderer/components/button/button"; import { Modal } from "@renderer/components/modal/modal"; import { useTranslation } from "react-i18next"; import "./modals.scss"; -import { Theme, UserProfile } from "@types"; +import { Theme } from "@types"; import { injectCustomCss, removeCustomCss } from "@renderer/helpers"; import { useToast } from "@renderer/hooks"; import { THEME_WEB_STORE_URL } from "@renderer/constants"; @@ -13,7 +13,8 @@ interface ImportThemeModalProps { onClose: () => void; onThemeImported: () => void; themeName: string; - authorCode: string; + authorId: string; + authorName: string; } export const ImportThemeModal = ({ @@ -21,23 +22,19 @@ export const ImportThemeModal = ({ onClose, onThemeImported, themeName, - authorCode, + authorId, + authorName, }: ImportThemeModalProps) => { const { t } = useTranslation("settings"); const { showSuccessToast, showErrorToast } = useToast(); - let author: UserProfile | null = null; - window.electron.getUser(authorCode).then((user) => { - author = user; - }); - const handleImportTheme = async () => { const theme: Theme = { id: crypto.randomUUID(), name: themeName, isActive: false, - author: author?.id, - authorName: author?.displayName, + author: authorId, + authorName: authorName, code: `${THEME_WEB_STORE_URL}/themes/${themeName.toLowerCase()}/theme.css`, createdAt: new Date(), updatedAt: new Date(), diff --git a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx index d14b4fe7..9cd0e52e 100644 --- a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx +++ b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx @@ -6,19 +6,22 @@ import { ImportThemeModal } from "./modals/import-theme-modal"; interface SettingsAppearanceProps { appearanceTheme: string | null; - appearanceAuthor: string | null; + appearanceAuthorId: string | null; + appearanceAuthorName: string | null; } export const SettingsAppearance = ({ appearanceTheme, - appearanceAuthor, + appearanceAuthorId, + appearanceAuthorName, }: SettingsAppearanceProps) => { const [themes, setThemes] = useState([]); const [isImportThemeModalVisible, setIsImportThemeModalVisible] = useState(false); const [importTheme, setImportTheme] = useState<{ theme: string; - author: string; + authorId: string; + authorName: string; } | null>(null); const loadThemes = async () => { @@ -39,14 +42,15 @@ export const SettingsAppearance = ({ }, []); useEffect(() => { - if (appearanceTheme && appearanceAuthor) { + if (appearanceTheme && appearanceAuthorId && appearanceAuthorName) { setIsImportThemeModalVisible(true); setImportTheme({ theme: appearanceTheme, - author: appearanceAuthor, + authorId: appearanceAuthorId, + authorName: appearanceAuthorName, }); } - }, [appearanceTheme, appearanceAuthor]); + }, [appearanceTheme, appearanceAuthorId, appearanceAuthorName]); return (
@@ -81,7 +85,8 @@ export const SettingsAppearance = ({ loadThemes(); }} themeName={importTheme.theme} - authorCode={importTheme.author} + authorId={importTheme.authorId} + authorName={importTheme.authorName} /> )}
diff --git a/src/renderer/src/pages/settings/settings.tsx b/src/renderer/src/pages/settings/settings.tsx index 4fa7821c..9c66aa6a 100644 --- a/src/renderer/src/pages/settings/settings.tsx +++ b/src/renderer/src/pages/settings/settings.tsx @@ -39,7 +39,11 @@ export default function Settings() { { tabLabel: ( <> - TorBox + TorBox Torbox ), @@ -65,7 +69,8 @@ export default function Settings() { currentCategoryIndex, setCurrentCategoryIndex, appearanceTheme, - appearanceAuthor, + appearanceAuthorId, + appearanceAuthorName, }) => { const renderCategory = () => { if (currentCategoryIndex === 0) { @@ -84,7 +89,8 @@ export default function Settings() { return ( ); } From d1a77dc5ece91a52048f02282875b4a78320408f Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sun, 16 Feb 2025 17:41:10 -0300 Subject: [PATCH 259/291] lint --- .../src/context/settings/settings.context.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/context/settings/settings.context.tsx b/src/renderer/src/context/settings/settings.context.tsx index 8aa22f14..77e78bce 100644 --- a/src/renderer/src/context/settings/settings.context.tsx +++ b/src/renderer/src/context/settings/settings.context.tsx @@ -47,9 +47,9 @@ export function SettingsContextProvider({ const [appearanceAuthorId, setAppearanceAuthorId] = useState( null ); - const [appearanceAuthorName, setAppearanceAuthorName] = useState( - null - ); + const [appearanceAuthorName, setAppearanceAuthorName] = useState< + string | null + >(null); const [currentCategoryIndex, setCurrentCategoryIndex] = useState(0); const [blockedUsers, setBlockedUsers] = useState([]); @@ -75,7 +75,11 @@ export function SettingsContextProvider({ }, [appearanceTheme]); useEffect(() => { - if (defaultAppearanceTheme && defaultAppearanceAuthorId && defaultAppearanceAuthorName) { + if ( + defaultAppearanceTheme && + defaultAppearanceAuthorId && + defaultAppearanceAuthorName + ) { setAppearanceTheme(defaultAppearanceTheme); setAppearanceAuthorId(defaultAppearanceAuthorId); setAppearanceAuthorName(defaultAppearanceAuthorName); From cc14562edda4395df812f8398558675b4d974593 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sun, 16 Feb 2025 17:52:28 -0300 Subject: [PATCH 260/291] fix: add space in Torbox tab label --- src/renderer/src/pages/settings/settings.tsx | 28 ++++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/renderer/src/pages/settings/settings.tsx b/src/renderer/src/pages/settings/settings.tsx index 9c66aa6a..dc0fef2b 100644 --- a/src/renderer/src/pages/settings/settings.tsx +++ b/src/renderer/src/pages/settings/settings.tsx @@ -36,20 +36,20 @@ export default function Settings() { }, ...(isTorboxEnabled ? [ - { - tabLabel: ( - <> - TorBox - Torbox - - ), - contentTitle: "TorBox", - }, - ] + { + tabLabel: ( + <> + TorBox{" "} + Torbox + + ), + contentTitle: "TorBox", + }, + ] : []), { tabLabel: "Real-Debrid", contentTitle: "Real-Debrid" }, ]; From f7acb44265732c9f8482d3a2112a0eb27231938c Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sun, 16 Feb 2025 17:53:12 -0300 Subject: [PATCH 261/291] refactor: improve Torbox tab label rendering --- src/renderer/src/pages/settings/settings.tsx | 28 ++++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/renderer/src/pages/settings/settings.tsx b/src/renderer/src/pages/settings/settings.tsx index dc0fef2b..1cfd499f 100644 --- a/src/renderer/src/pages/settings/settings.tsx +++ b/src/renderer/src/pages/settings/settings.tsx @@ -36,20 +36,20 @@ export default function Settings() { }, ...(isTorboxEnabled ? [ - { - tabLabel: ( - <> - TorBox{" "} - Torbox - - ), - contentTitle: "TorBox", - }, - ] + { + tabLabel: ( + <> + TorBox{" "} + Torbox + + ), + contentTitle: "TorBox", + }, + ] : []), { tabLabel: "Real-Debrid", contentTitle: "Real-Debrid" }, ]; From 5a6f66a556b71d89ff82a2a4952c6a3c9a5700b1 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sun, 16 Feb 2025 18:36:46 -0300 Subject: [PATCH 262/291] feat: add theme import translations --- src/locales/en/translation.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index ef4e3b56..f1e85019 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -328,7 +328,11 @@ "torbox_description": "TorBox is your premium seedbox service rivaling even the best servers on the market.", "torbox_account_linked": "TorBox account linked", "real_debrid_account_linked": "Real-Debrid account linked", - "name_min_length": "Theme name must be at least 3 characters long" + "name_min_length": "Theme name must be at least 3 characters long", + "import_theme": "Import theme", + "import_theme_description": "You will import {{theme}} from the theme store", + "error_importing_theme": "Error importing theme", + "theme_imported": "Theme imported successfully" }, "notifications": { "download_complete": "Download complete", From f1e47fc3036ff9df6ccd0583357a40e0578ab4b1 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 16 Feb 2025 21:41:34 +0000 Subject: [PATCH 263/291] feat: killing port on start --- package.json | 1 + src/main/index.ts | 3 ++- yarn.lock | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9435b728..fd7c2855 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "i18next-browser-languagedetector": "^7.2.1", "jsdom": "^24.0.0", "jsonwebtoken": "^9.0.2", + "kill-port": "^2.0.1", "knex": "^3.1.0", "lodash-es": "^4.17.21", "parse-torrent": "^11.0.17", diff --git a/src/main/index.ts b/src/main/index.ts index 9e91c349..0792f826 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -3,6 +3,7 @@ import updater from "electron-updater"; import i18n from "i18next"; import path from "node:path"; import url from "node:url"; +import kill from "kill-port"; import { electronApp, optimizer } from "@electron-toolkit/utils"; import { logger, WindowManager } from "@main/services"; import resources from "@locales"; @@ -58,7 +59,7 @@ app.whenReady().then(async () => { return net.fetch(url.pathToFileURL(decodeURI(filePath)).toString()); }); - await loadState(); + await kill(PythonRPC.RPC_PORT).finally(() => loadState()); const language = await db.get(levelKeys.language, { valueEncoding: "utf-8", diff --git a/yarn.lock b/yarn.lock index 29355406..58d0cc9a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5865,6 +5865,11 @@ get-symbol-description@^1.1.0: es-errors "^1.3.0" get-intrinsic "^1.2.6" +get-them-args@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/get-them-args/-/get-them-args-1.3.2.tgz#74a20ba8a4abece5ae199ad03f2bcc68fdfc9ba5" + integrity sha512-LRn8Jlk+DwZE4GTlDbT3Hikd1wSHgLMme/+7ddlqKd7ldwR6LjJgTVWzBnR01wnYGe4KgrXjg287RaI22UHmAw== + getopts@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4" @@ -6890,6 +6895,14 @@ keyv@^4.0.0, keyv@^4.5.3: dependencies: json-buffer "3.0.1" +kill-port@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/kill-port/-/kill-port-2.0.1.tgz#e5e18e2706b13d54320938be42cb7d40609b15cf" + integrity sha512-e0SVOV5jFo0mx8r7bS29maVWp17qGqLBZ5ricNSajON6//kmb7qqqNnml4twNE8Dtj97UQD+gNFOaipS/q1zzQ== + dependencies: + get-them-args "1.3.2" + shell-exec "1.0.2" + knex@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/knex/-/knex-3.1.0.tgz#b6ddd5b5ad26a6315234a5b09ec38dc4a370bd8c" @@ -8613,6 +8626,11 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shell-exec@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/shell-exec/-/shell-exec-1.0.2.tgz#2e9361b0fde1d73f476c4b6671fa17785f696756" + integrity sha512-jyVd+kU2X+mWKMmGhx4fpWbPsjvD53k9ivqetutVW/BQ+WIZoDoP4d8vUMGezV6saZsiNoW2f9GIhg9Dondohg== + side-channel-list@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" From 2246775046bae29af16608dff087778c931f0b74 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 16 Feb 2025 21:52:57 +0000 Subject: [PATCH 264/291] feat: clearing theme --- .../src/context/settings/settings.context.tsx | 60 ++++++++++++------- .../settings/aparence/settings-appearance.tsx | 52 +++++++++------- .../src/pages/settings/settings-account.tsx | 2 +- src/renderer/src/pages/settings/settings.tsx | 16 +---- 4 files changed, 70 insertions(+), 60 deletions(-) diff --git a/src/renderer/src/context/settings/settings.context.tsx b/src/renderer/src/context/settings/settings.context.tsx index 77e78bce..61a3fdb7 100644 --- a/src/renderer/src/context/settings/settings.context.tsx +++ b/src/renderer/src/context/settings/settings.context.tsx @@ -9,26 +9,32 @@ export interface SettingsContext { updateUserPreferences: (values: Partial) => Promise; setCurrentCategoryIndex: React.Dispatch>; clearSourceUrl: () => void; + clearTheme: () => void; sourceUrl: string | null; currentCategoryIndex: number; blockedUsers: UserBlocks["blocks"]; fetchBlockedUsers: () => Promise; - appearanceTheme: string | null; - appearanceAuthorId: string | null; - appearanceAuthorName: string | null; + appearance: { + theme: string | null; + authorId: string | null; + authorName: string | null; + }; } export const settingsContext = createContext({ updateUserPreferences: async () => {}, setCurrentCategoryIndex: () => {}, clearSourceUrl: () => {}, + clearTheme: () => {}, sourceUrl: null, currentCategoryIndex: 0, blockedUsers: [], fetchBlockedUsers: async () => {}, - appearanceTheme: null, - appearanceAuthorId: null, - appearanceAuthorName: null, + appearance: { + theme: null, + authorId: null, + authorName: null, + }, }); const { Provider } = settingsContext; @@ -43,15 +49,16 @@ export function SettingsContextProvider({ }: Readonly) { const dispatch = useAppDispatch(); const [sourceUrl, setSourceUrl] = useState(null); - const [appearanceTheme, setAppearanceTheme] = useState(null); - const [appearanceAuthorId, setAppearanceAuthorId] = useState( - null - ); - const [appearanceAuthorName, setAppearanceAuthorName] = useState< - string | null - >(null); + const [appearance, setAppearance] = useState<{ + theme: string | null; + authorId: string | null; + authorName: string | null; + }>({ + theme: null, + authorId: null, + authorName: null, + }); const [currentCategoryIndex, setCurrentCategoryIndex] = useState(0); - const [blockedUsers, setBlockedUsers] = useState([]); const [searchParams] = useSearchParams(); @@ -71,8 +78,8 @@ export function SettingsContextProvider({ }, [defaultSourceUrl]); useEffect(() => { - if (appearanceTheme) setCurrentCategoryIndex(3); - }, [appearanceTheme]); + if (appearance.theme) setCurrentCategoryIndex(3); + }, [appearance.theme]); useEffect(() => { if ( @@ -80,9 +87,11 @@ export function SettingsContextProvider({ defaultAppearanceAuthorId && defaultAppearanceAuthorName ) { - setAppearanceTheme(defaultAppearanceTheme); - setAppearanceAuthorId(defaultAppearanceAuthorId); - setAppearanceAuthorName(defaultAppearanceAuthorName); + setAppearance({ + theme: defaultAppearanceTheme, + authorId: defaultAppearanceAuthorId, + authorName: defaultAppearanceAuthorName, + }); } }, [ defaultAppearanceTheme, @@ -90,6 +99,14 @@ export function SettingsContextProvider({ defaultAppearanceAuthorName, ]); + const clearTheme = useCallback(() => { + setAppearance({ + theme: null, + authorId: null, + authorName: null, + }); + }, []); + const fetchBlockedUsers = useCallback(async () => { const blockedUsers = await window.electron.getBlockedUsers(12, 0); setBlockedUsers(blockedUsers.blocks); @@ -115,12 +132,11 @@ export function SettingsContextProvider({ setCurrentCategoryIndex, clearSourceUrl, fetchBlockedUsers, + clearTheme, currentCategoryIndex, sourceUrl, blockedUsers, - appearanceTheme, - appearanceAuthorId, - appearanceAuthorName, + appearance, }} > {children} diff --git a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx index 9cd0e52e..cf72bf4c 100644 --- a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx +++ b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx @@ -1,20 +1,21 @@ -import { useEffect, useState } from "react"; +import { useCallback, useContext, useEffect, useState } from "react"; import "./settings-appearance.scss"; import { ThemeActions, ThemeCard, ThemePlaceholder } from "./index"; import type { Theme } from "@types"; import { ImportThemeModal } from "./modals/import-theme-modal"; +import { settingsContext } from "@renderer/context"; interface SettingsAppearanceProps { - appearanceTheme: string | null; - appearanceAuthorId: string | null; - appearanceAuthorName: string | null; + appearance: { + theme: string | null; + authorId: string | null; + authorName: string | null; + }; } -export const SettingsAppearance = ({ - appearanceTheme, - appearanceAuthorId, - appearanceAuthorName, -}: SettingsAppearanceProps) => { +export function SettingsAppearance({ + appearance, +}: Readonly) { const [themes, setThemes] = useState([]); const [isImportThemeModalVisible, setIsImportThemeModalVisible] = useState(false); @@ -24,14 +25,16 @@ export const SettingsAppearance = ({ authorName: string; } | null>(null); - const loadThemes = async () => { + const { clearTheme } = useContext(settingsContext); + + const loadThemes = useCallback(async () => { const themesList = await window.electron.getAllCustomThemes(); setThemes(themesList); - }; + }, []); useEffect(() => { loadThemes(); - }, []); + }, [loadThemes]); useEffect(() => { const unsubscribe = window.electron.onCssInjected(() => { @@ -39,18 +42,24 @@ export const SettingsAppearance = ({ }); return () => unsubscribe(); - }, []); + }, [loadThemes]); useEffect(() => { - if (appearanceTheme && appearanceAuthorId && appearanceAuthorName) { + if (appearance.theme && appearance.authorId && appearance.authorName) { setIsImportThemeModalVisible(true); setImportTheme({ - theme: appearanceTheme, - authorId: appearanceAuthorId, - authorName: appearanceAuthorName, + theme: appearance.theme, + authorId: appearance.authorId, + authorName: appearance.authorName, }); } - }, [appearanceTheme, appearanceAuthorId, appearanceAuthorName]); + }, [appearance.theme, appearance.authorId, appearance.authorName]); + + const onThemeImported = useCallback(() => { + setIsImportThemeModalVisible(false); + loadThemes(); + clearTheme(); + }, [clearTheme, loadThemes]); return (
@@ -80,10 +89,7 @@ export const SettingsAppearance = ({ setIsImportThemeModalVisible(false)} - onThemeImported={() => { - setIsImportThemeModalVisible(false); - loadThemes(); - }} + onThemeImported={onThemeImported} themeName={importTheme.theme} authorId={importTheme.authorId} authorName={importTheme.authorName} @@ -91,4 +97,4 @@ export const SettingsAppearance = ({ )}
); -}; +} diff --git a/src/renderer/src/pages/settings/settings-account.tsx b/src/renderer/src/pages/settings/settings-account.tsx index 06aabc06..9cf35541 100644 --- a/src/renderer/src/pages/settings/settings-account.tsx +++ b/src/renderer/src/pages/settings/settings-account.tsx @@ -63,7 +63,7 @@ export function SettingsAccount() { return () => { unsubscribe(); }; - }, [fetchUserDetails, updateUserDetails, showSuccessToast]); + }, [fetchUserDetails, updateUserDetails, t, showSuccessToast]); const visibilityOptions = [ { value: "PUBLIC", label: t("public") }, diff --git a/src/renderer/src/pages/settings/settings.tsx b/src/renderer/src/pages/settings/settings.tsx index 1cfd499f..391742b8 100644 --- a/src/renderer/src/pages/settings/settings.tsx +++ b/src/renderer/src/pages/settings/settings.tsx @@ -65,13 +65,7 @@ export default function Settings() { return ( - {({ - currentCategoryIndex, - setCurrentCategoryIndex, - appearanceTheme, - appearanceAuthorId, - appearanceAuthorName, - }) => { + {({ currentCategoryIndex, setCurrentCategoryIndex, appearance }) => { const renderCategory = () => { if (currentCategoryIndex === 0) { return ; @@ -86,13 +80,7 @@ export default function Settings() { } if (currentCategoryIndex === 3) { - return ( - - ); + return ; } if (currentCategoryIndex === 4) { From e8d5c621ea4d980166c1b3cdbde663bdf13d61fb Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sun, 16 Feb 2025 19:08:47 -0300 Subject: [PATCH 265/291] fix: improve theme import modal close behavior --- .../src/pages/settings/aparence/settings-appearance.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx index cf72bf4c..625f1a90 100644 --- a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx +++ b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx @@ -58,7 +58,6 @@ export function SettingsAppearance({ const onThemeImported = useCallback(() => { setIsImportThemeModalVisible(false); loadThemes(); - clearTheme(); }, [clearTheme, loadThemes]); return ( @@ -88,7 +87,10 @@ export function SettingsAppearance({ {importTheme && ( setIsImportThemeModalVisible(false)} + onClose={() => { + setIsImportThemeModalVisible(false); + clearTheme(); + }} onThemeImported={onThemeImported} themeName={importTheme.theme} authorId={importTheme.authorId} From 385e29262caf54fcbfcf256a09d3e17767e8c5e1 Mon Sep 17 00:00:00 2001 From: hydrasources Date: Mon, 17 Feb 2025 08:29:52 +0300 Subject: [PATCH 266/291] Update translation.json --- src/locales/ru/translation.json | 39 +++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index bbd7047a..d6c3f581 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -183,7 +183,13 @@ "no_write_permission": "Невозможно загрузить в эту директорию. Нажмите здесь, чтобы узнать больше.", "reset_achievements_title": "Вы уверены?", "reset_achievements_success": "Достижения успешно сброшены", - "reset_achievements_error": "Не удалось сбросить достижения" + "reset_achievements_error": "Не удалось сбросить достижения", + "download_error_gofile_quota_exceeded": "Вы превысили месячную квоту Gofile. Пожалуйста, подождите, пока квота не будет восстановлена.", + "download_error_real_debrid_account_not_authorized": "Ваш аккаунт Real-Debrid не авторизован для осуществления новых загрузок. Пожалуйста, проверьте настройки учетной записи и повторите попытку.", + "download_error_not_cached_in_real_debrid": "Эта загрузка недоступна на Real-Debrid, а опрос статуса загрузки с Real-Debrid пока недоступен.", + "download_error_not_cached_in_torbox": "Эта загрузка недоступна на Torbox, и опросить статус загрузки с Torbox пока невозможно.", + "game_added_to_favorites": "Игра добавлена в избранное", + "game_removed_from_favorites": "Игра удалена из избранного" }, "activation": { "title": "Активировать Hydra", @@ -295,7 +301,36 @@ "become_subscriber": "Станьте обладателем Hydra Cloud", "subscription_renew_cancelled": "Автоматическое продление отключено", "subscription_renews_on": "Ваша подписка продлевается на {{date}}", - "bill_sent_until": "Ваш следующий счет будет отправлен до этого дня" + "bill_sent_until": "Ваш следующий счет будет отправлен до этого дня", + "no_themes": "Похоже, что у вас еще нет тем, но не волнуйтесь, нажмите здесь, чтобы создать свой первый шедевр", + "editor_tab_code": "Код", + "editor_tab_info": "Информация", + "editor_tab_save": "Сохранить", + "web_store": "Веб-магазин", + "clear_themes": "Очистить", + "create_theme": "Создать", + "create_theme_modal_title": "Создать пользовательскую тему", + "create_theme_modal_description": "Создать новую тему для настройки внешнего вида Hydra", + "theme_name": "Name", + "insert_theme_name": "Вставить название темы", + "set_theme": "Установить тему", + "unset_theme": "Снять тему", + "delete_theme": "Удалить тему", + "edit_theme": "Редактировать тему", + "delete_all_themes": "Удалить все темы", + "delete_all_themes_description": "Это удалит все ваши пользовательские темы", + "delete_theme_description": "Это приведет к удалению темы {{theme}}", + "cancel": "Отменить", + "appearance": "Внешний вид", + "enable_torbox": "Включить Torbox", + "torbox_description": "TorBox - это ваш премиум-сервис, конкурирующий даже с лучшими серверами на рынке.", + "torbox_account_linked": "Аккаунт TorBox привязан", + "real_debrid_account_linked": "Аккаунт Real-Debrid привязан", + "name_min_length": "Название темы должно содержать не менее 3 символов", + "import_theme": "Импортировать тему", + "import_theme_description": "Вы импортируете {{theme}} из магазина тем", + "error_importing_theme": "Ошибка при импорте темы", + "theme_imported": "Тема успешно импортирована" }, "notifications": { "download_complete": "Загрузка завершена", From ad551650782c0e5ecbe2c53cc2e8b2edf59e5570 Mon Sep 17 00:00:00 2001 From: hydrasources Date: Mon, 17 Feb 2025 11:21:15 +0300 Subject: [PATCH 267/291] Update translation.json --- src/locales/ru/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index d6c3f581..c2aa40ce 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -311,7 +311,7 @@ "create_theme": "Создать", "create_theme_modal_title": "Создать пользовательскую тему", "create_theme_modal_description": "Создать новую тему для настройки внешнего вида Hydra", - "theme_name": "Name", + "theme_name": "Название", "insert_theme_name": "Вставить название темы", "set_theme": "Установить тему", "unset_theme": "Снять тему", From e695d599de0e2812ac6ad4775fe12947cafd46a7 Mon Sep 17 00:00:00 2001 From: 7ROBE <75377870+7ROBE@users.noreply.github.com> Date: Mon, 17 Feb 2025 22:38:36 +0300 Subject: [PATCH 268/291] Update translation.json --- src/locales/ar/translation.json | 230 ++++++++++++++++++-------------- 1 file changed, 133 insertions(+), 97 deletions(-) diff --git a/src/locales/ar/translation.json b/src/locales/ar/translation.json index a12b3559..13445e9d 100644 --- a/src/locales/ar/translation.json +++ b/src/locales/ar/translation.json @@ -7,18 +7,18 @@ "featured": "مميز", "surprise_me": "مفاجئني", "no_results": "لم يتم العثور على نتائج", - "start_typing": "ابدأ الكتابة للبحث...", - "hot": "الأكثر شيوعًا الآن", + "start_typing": "ابدأ بالكتابة للبحث...", + "hot": "الأكثر شهرة الآن", "weekly": "📅 أفضل ألعاب الأسبوع", - "achievements": "🏆 ألعاب للتغلب عليها" + "achievements": "🏆 ألعاب يجب إكمالها" }, "sidebar": { - "catalogue": "الكـتالوج", + "catalogue": "الفهرس", "downloads": "التنزيلات", "settings": "الإعدادات", "my_library": "مكتبتي", - "downloading_metadata": "{{title}} (جارٍ تنزيل البيانات الوصفية...)", - "paused": "{{title}} (معلّق)", + "downloading_metadata": "{{title}} (جاري تنزيل البيانات الوصفية...)", + "paused": "{{title}} (معلق)", "downloading": "{{title}} ({{percentage}} - جاري التنزيل...)", "filter": "تصفية المكتبة", "home": "الرئيسية", @@ -26,12 +26,13 @@ "game_has_no_executable": "اللعبة لا تحتوي على ملف تشغيل", "sign_in": "تسجيل الدخول", "friends": "الأصدقاء", - "need_help": "تحتاج مساعدة؟" + "need_help": "تحتاج مساعدة؟", + "favorites": "المفضلة" }, "header": { - "search": "ابحث عن الألعاب", + "search": "بحث الألعاب", "home": "الرئيسية", - "catalogue": "الكـتالوج", + "catalogue": "الفهرس", "downloads": "التنزيلات", "search_results": "نتائج البحث", "settings": "الإعدادات", @@ -40,16 +41,16 @@ }, "bottom_panel": { "no_downloads_in_progress": "لا توجد تنزيلات قيد التقدم", - "downloading_metadata": "جارٍ تنزيل البيانات الوصفية لـ {{title}}...", - "downloading": "جارٍ تنزيل {{title}}... ({{percentage}} اكتمال) - الوقت المتبقي {{eta}} - السرعة {{speed}}", - "calculating_eta": "جارٍ تنزيل {{title}}... ({{percentage}} اكتمال) - جاري حساب الوقت المتبقي...", - "checking_files": "جارٍ فحص ملفات {{title}}... ({{percentage}} اكتمال)" + "downloading_metadata": "جاري تنزيل بيانات {{title}} الوصفية...", + "downloading": "جاري تنزيل {{title}}... ({{percentage}} مكتمل) - الوقت المتبقي {{eta}} - السرعة {{speed}}", + "calculating_eta": "جاري تنزيل {{title}}... ({{percentage}} مكتمل) - جاري حساب الوقت المتبقي...", + "checking_files": "جاري فحص ملفات {{title}}... ({{percentage}} مكتمل)" }, "catalogue": { "search": "تصفية...", "developers": "المطورون", "genres": "الأنواع", - "tags": "العلامات", + "tags": "الوسوم", "publishers": "الناشرون", "download_sources": "مصادر التنزيل", "result_count": "{{resultCount}} نتيجة", @@ -68,34 +69,34 @@ "cancel": "إلغاء", "remove": "إزالة", "space_left_on_disk": "{{space}} متبقي على القرص", - "eta": "الانتهاء {{eta}}", - "calculating_eta": "جارٍ حساب الوقت المتبقي...", - "downloading_metadata": "جارٍ تنزيل البيانات الوصفية...", - "filter": "تصفية الحزم المعاد تعبئتها", + "eta": "الانتهاء المتوقع {{eta}}", + "calculating_eta": "جاري حساب الوقت المتبقي...", + "downloading_metadata": "جاري تنزيل البيانات الوصفية...", + "filter": "تصفية الإصدارات المعادة", "requirements": "متطلبات النظام", "minimum": "الحد الأدنى", - "recommended": "مُوصى به", - "paused": "معلّق", + "recommended": "مستحسن", + "paused": "معلق", "release_date": "تاريخ الإصدار {{date}}", "publisher": "نشر بواسطة {{publisher}}", "hours": "ساعات", "minutes": "دقائق", - "amount_hours": "{{amount}} ساعات", - "amount_minutes": "{{amount}} دقائق", + "amount_hours": "{{amount}} ساعة", + "amount_minutes": "{{amount}} دقيقة", "accuracy": "دقة {{accuracy}}%", "add_to_library": "إضافة إلى المكتبة", "remove_from_library": "إزالة من المكتبة", "no_downloads": "لا توجد تنزيلات متاحة", - "play_time": "لعب لمدة {{amount}}", - "last_time_played": "آخر تشغيل {{period}}", + "play_time": "وقت اللعب {{amount}}", + "last_time_played": "آخر مرة لعب {{period}}", "not_played_yet": "لم تلعب {{title}} بعد", "next_suggestion": "الاقتراح التالي", "play": "تشغيل", - "deleting": "جارٍ حذف المثبت...", + "deleting": "جاري حذف المثبت...", "close": "إغلاق", - "playing_now": "يتم التشغيل الآن", + "playing_now": "جاري التشغيل الآن", "change": "تغيير", - "repacks_modal_description": "اختر الحزمة المعاد تعبئتها التي تريد تنزيلها", + "repacks_modal_description": "اختر الإصدار المعاد الذي تريد تنزيله", "select_folder_hint": "لتغيير المجلد الافتراضي، انتقل إلى <0>الإعدادات", "download_now": "تنزيل الآن", "no_shop_details": "تعذر الحصول على تفاصيل المتجر.", @@ -110,12 +111,12 @@ "select_executable": "تحديد", "no_executable_selected": "لم يتم تحديد ملف تشغيل", "open_folder": "فتح المجلد", - "open_download_location": "عرض الملفات المحملة", + "open_download_location": "عرض الملفات المنزلة", "create_shortcut": "إنشاء اختصار على سطح المكتب", "clear": "مسح", "remove_files": "إزالة الملفات", "remove_from_library_title": "هل أنت متأكد؟", - "remove_from_library_description": "سيؤدي هذا إلى إزالة {{game}} من مكتبتك", + "remove_from_library_description": "سيتم إزالة {{game}} من مكتبتك", "options": "خيارات", "executable_section_title": "ملف التشغيل", "executable_section_description": "مسار الملف الذي سيتم تشغيله عند النقر على \"تشغيل\"", @@ -123,35 +124,35 @@ "downloads_section_description": "تحقق من التحديثات أو الإصدارات الأخرى لهذه اللعبة", "danger_zone_section_title": "منطقة الخطر", "danger_zone_section_description": "إزالة هذه اللعبة من مكتبتك أو الملفات التي تم تنزيلها بواسطة Hydra", - "download_in_progress": "تنزيل قيد التقدم", + "download_in_progress": "جاري التنزيل", "download_paused": "التنزيل معلق", "last_downloaded_option": "خيار التنزيل الأخير", "create_shortcut_success": "تم إنشاء الاختصار بنجاح", "create_shortcut_error": "خطأ في إنشاء الاختصار", "nsfw_content_title": "هذه اللعبة تحتوي على محتوى غير لائق", - "nsfw_content_description": "{{title}} يحتوي على محتوى قد لا يناسب جميع الأعمار. هل تريد المتابعة؟", + "nsfw_content_description": "{{title}} يحتوي على محتوى قد لا يكون مناسبًا لجميع الأعمار. هل تريد المتابعة؟", "allow_nsfw_content": "متابعة", "refuse_nsfw_content": "رجوع", "stats": "الإحصائيات", - "download_count": "مرات التنزيل", + "download_count": "التنزيلات", "player_count": "اللاعبون النشطون", "download_error": "خيار التنزيل هذا غير متاح", "download": "تنزيل", "executable_path_in_use": "مسار التشغيل مستخدم بالفعل بواسطة \"{{game}}\"", "warning": "تحذير:", - "hydra_needs_to_remain_open": "لهذا التنزيل، يجب أن يبقى Hydra مفتوحًا حتى اكتماله. إذا أغلق Hydra قبل الاكتمال، ستفقد تقدمك.", + "hydra_needs_to_remain_open": "لهذا التنزيل، يجب أن يظل Hydra مفتوحًا حتى اكتماله. إذا تم إغلاق Hydra قبل الاكتمال، ستفقد تقدمك.", "achievements": "الإنجازات", "achievements_count": "الإنجازات {{unlockedCount}}/{{achievementsCount}}", "cloud_save": "حفظ سحابي", - "cloud_save_description": "احفظ تقدمك على السحابة واستمر في اللعب من أي جهاز", + "cloud_save_description": "احفظ تقدمك في السحابة واستمر في اللعب من أي جهاز", "backups": "النسخ الاحتياطية", "install_backup": "تثبيت", "delete_backup": "حذف", "create_backup": "نسخة احتياطية جديدة", "last_backup_date": "آخر نسخة احتياطية في {{date}}", "no_backup_preview": "لم يتم العثور على حفظات لهذا العنوان", - "restoring_backup": "جارٍ استعادة النسخة الاحتياطية ({{progress}} اكتمال)...", - "uploading_backup": "جارٍ رفع النسخة الاحتياطية...", + "restoring_backup": "جاري استعادة النسخة الاحتياطية ({{progress}} مكتمل)...", + "uploading_backup": "جاري رفع النسخة الاحتياطية...", "no_backups": "لم تقم بإنشاء أي نسخ احتياطية لهذه اللعبة بعد", "backup_uploaded": "تم رفع النسخة الاحتياطية", "backup_deleted": "تم حذف النسخة الاحتياطية", @@ -164,61 +165,67 @@ "files_automatically_mapped": "تم تعيين الملفات تلقائيًا", "no_backups_created": "لم يتم إنشاء نسخ احتياطية لهذه اللعبة", "manage_files": "إدارة الملفات", - "loading_save_preview": "جارٍ البحث عن حفظات الألعاب...", + "loading_save_preview": "جاري البحث عن حفظات اللعبة...", "wine_prefix": "بادئة Wine", "wine_prefix_description": "بادئة Wine المستخدمة لتشغيل هذه اللعبة", "launch_options": "خيارات التشغيل", "launch_options_description": "يمكن للمستخدمين المتقدمين إدخال تعديلات على خيارات التشغيل (ميزة تجريبية)", - "launch_options_placeholder": "لم يتم تحديد أي معاملات", + "launch_options_placeholder": "لا توجد معلمات محددة", "no_download_option_info": "لا توجد معلومات متاحة", - "backup_deletion_failed": "فشل حذف النسخة الاحتياطية", - "max_number_of_artifacts_reached": "تم الوصول إلى الحد الأقصى لعدد النسخ الاحتياطية لهذه اللعبة", - "achievements_not_sync": "تعرف على كيفية مزامنة إنجازاتك", + "backup_deletion_failed": "فشل في حذف النسخة الاحتياطية", + "max_number_of_artifacts_reached": "تم الوصول إلى الحد الأقصى من النسخ الاحتياطية لهذه اللعبة", + "achievements_not_sync": "شاهد كيفية مزامنة إنجازاتك", "manage_files_description": "إدارة الملفات التي سيتم نسخها احتياطيًا واستعادتها", "select_folder": "حدد المجلد", "backup_from": "نسخة احتياطية من {{date}}", "custom_backup_location_set": "تم تعيين موقع نسخ احتياطي مخصص", "no_directory_selected": "لم يتم تحديد مجلد", - "no_write_permission": "لا يمكن التنزيل إلى هذا المجلد. انقر هنا لمعرفة المزيد.", + "no_write_permission": "لا يمكن التنزيل إلى هذا المجلد. انقر هنا للمزيد من المعلومات.", "reset_achievements": "إعادة تعيين الإنجازات", "reset_achievements_description": "سيؤدي هذا إلى إعادة تعيين جميع إنجازات {{game}}", "reset_achievements_title": "هل أنت متأكد؟", "reset_achievements_success": "تم إعادة تعيين الإنجازات بنجاح", - "reset_achievements_error": "فشل إعادة تعيين الإنجازات" + "reset_achievements_error": "فشل في إعادة تعيين الإنجازات", + "download_error_gofile_quota_exceeded": "لقد تجاوزت الحصة الشهرية لـ Gofile. يرجى الانتظار حتى إعادة تعيين الحصة.", + "download_error_real_debrid_account_not_authorized": "حساب Real-Debrid الخاص بك غير مصرح له بإجراء تنزيلات جديدة. يرجى مراجعة إعدادات الحساب والمحاولة مرة أخرى.", + "download_error_not_cached_in_real_debrid": "هذا التنزيل غير متوفر على Real-Debrid وجلب حالة التنزيل من Real-Debrid غير متاح حاليًا.", + "download_error_not_cached_in_torbox": "هذا التنزيل غير متوفر على Torbox وجلب حالة التنزيل من Torbox غير متاح حاليًا.", + "game_removed_from_favorites": "تمت إزالة اللعبة من المفضلة", + "game_added_to_favorites": "تمت إضافة اللعبة إلى المفضلة" }, "activation": { "title": "تفعيل Hydra", "installation_id": "معرف التثبيت:", "enter_activation_code": "أدخل رمز التفعيل الخاص بك", - "message": "إذا كنت لا تعرف أين تطلب هذا، فلا يجب أن يكون لديك هذا.", + "message": "إذا كنت لا تعرف أين تطلب هذا، فأنت لا يجب أن يكون لديك هذا.", "activate": "تفعيل", - "loading": "جارٍ التحميل..." + "loading": "جاري التحميل..." }, "downloads": { "resume": "استئناف", "pause": "إيقاف مؤقت", - "eta": "الانتهاء {{eta}}", - "paused": "معلّق", - "verifying": "جارٍ التحقق...", + "eta": "الانتهاء المتوقع {{eta}}", + "paused": "معلق", + "verifying": "جاري التحقق...", "completed": "مكتمل", - "removed": "غير محمل", + "removed": "غير منزّل", "cancel": "إلغاء", - "filter": "تصفية الألعاب المحملة", + "filter": "تصفية الألعاب المنزلة", "remove": "إزالة", - "downloading_metadata": "جارٍ تنزيل البيانات الوصفية...", - "deleting": "جارٍ حذف المثبت...", - "delete": "إزالة المثبت", + "downloading_metadata": "جاري تنزيل البيانات الوصفية...", + "deleting": "جاري حذف المثبت...", + "delete": "حذف المثبت", "delete_modal_title": "هل أنت متأكد؟", "delete_modal_description": "سيؤدي هذا إلى إزالة جميع ملفات التثبيت من جهازك", "install": "تثبيت", "download_in_progress": "قيد التقدم", "queued_downloads": "التنزيلات في قائمة الانتظار", - "downloads_completed": "مكتمل", + "downloads_completed": "مكتملة", "queued": "في قائمة الانتظار", - "no_downloads_title": "فارغ جدًا", + "no_downloads_title": "لا شيء هنا", "no_downloads_description": "لم تقم بتنزيل أي شيء باستخدام Hydra بعد، ولكن لم يفت الأوان للبدء.", - "checking_files": "جارٍ فحص الملفات...", - "seeding": "التوزيع", + "checking_files": "جاري فحص الملفات...", + "seeding": "جاري التوزيع", "stop_seeding": "إيقاف التوزيع", "resume_seeding": "استئناف التوزيع", "options": "إدارة" @@ -228,8 +235,8 @@ "change": "تحديث", "notifications": "الإشعارات", "enable_download_notifications": "عند اكتمال التنزيل", - "enable_repack_list_notifications": "عند إضافة حزمة معاد تعبئتها جديدة", - "real_debrid_api_token_label": "رمز واجهة برمجة تطبيقات Real-Debrid", + "enable_repack_list_notifications": "عند إضافة إصدار معاد جديد", + "real_debrid_api_token_label": "رمز Real-Debrid API", "quit_app_instead_hiding": "لا تخفي Hydra عند الإغلاق", "launch_with_system": "تشغيل Hydra مع بدء النظام", "general": "عام", @@ -238,21 +245,21 @@ "language": "اللغة", "api_token": "رمز API", "enable_real_debrid": "تفعيل Real-Debrid", - "real_debrid_description": "Real-Debrid هو أداة تنزيل غير مقيدة تتيح لك تنزيل الملفات بسرعة، مقيدة فقط بسرعة الإنترنت لديك.", + "real_debrid_description": "Real-Debrid هو أداة تنزيل غير مقيدة تتيح لك تنزيل الملفات بسرعة، محدودة فقط بسرعة اتصالك بالإنترنت.", "debrid_invalid_token": "رمز API غير صالح", "debrid_api_token_hint": "يمكنك الحصول على رمز API الخاص بك <0>هنا", - "real_debrid_free_account_error": "الحساب \"{{username}}\" هو حساب مجاني. يرجى الاشتراك في Real-Debrid", + "real_debrid_free_account_error": "الحساب \"{{username}}\" حساب مجاني. يرجى الاشتراك في Real-Debrid", "debrid_linked_message": "تم ربط الحساب \"{{username}}\"", "save_changes": "حفظ التغييرات", "changes_saved": "تم حفظ التغييرات بنجاح", - "download_sources_description": "سيقوم Hydra بجلب روابط التنزيل من هذه المصادر. يجب أن يكون عنوان URL المصدر رابطًا مباشرًا لملف .json يحتوي على روابط التنزيل.", + "download_sources_description": "سيقوم Hydra بجلب روابط التنزيل من هذه المصادر. يجب أن يكون عنوان URL للمصدر رابطًا مباشرًا لملف .json يحتوي على روابط التنزيل.", "validate_download_source": "تحقق", "remove_download_source": "إزالة", "add_download_source": "إضافة مصدر", "download_count_zero": "لا توجد خيارات تنزيل", "download_count_one": "{{countFormatted}} خيار تنزيل", "download_count_other": "{{countFormatted}} خيارات تنزيل", - "download_source_url": "عنوان URL لمصدر التنزيل", + "download_source_url": "عنوان مصدر التنزيل", "add_download_source_description": "أدخل عنوان URL لملف .json", "download_source_up_to_date": "محدث", "download_source_errored": "خطأ", @@ -272,13 +279,13 @@ "profile_visibility": "رؤية الملف الشخصي", "profile_visibility_description": "اختر من يمكنه رؤية ملفك الشخصي ومكتبتك", "required_field": "هذا الحقل مطلوب", - "source_already_exists": "تمت إضافة هذا المصدر مسبقًا", - "must_be_valid_url": "يجب أن يكون المصدر عنوان URL صالحًا", + "source_already_exists": "هذا المصدر مضاف مسبقًا", + "must_be_valid_url": "يجب أن يكون المصدر عنوان URL صالح", "blocked_users": "المستخدمون المحظورون", "user_unblocked": "تم إلغاء حظر المستخدم", "enable_achievement_notifications": "عند فتح إنجاز", "launch_minimized": "تشغيل Hydra مصغرًا", - "disable_nsfw_alert": "تعطيل تنبيه المحتوى غير اللائق", + "disable_nsfw_alert": "تعطيل تنبيهات المحتوى غير اللائق", "seed_after_download_complete": "التوزيع بعد اكتمال التنزيل", "show_hidden_achievement_description": "عرض وصف الإنجازات المخفية قبل فتحها", "account": "الحساب", @@ -296,18 +303,47 @@ "become_subscriber": "كن مشتركًا في Hydra Cloud", "subscription_renew_cancelled": "تم تعطيل التجديد التلقائي", "subscription_renews_on": "سيتم تجديد اشتراكك في {{date}}", - "bill_sent_until": "سيتم إرسال فاتورتك التالية حتى هذا اليوم" + "bill_sent_until": "سيتم إرسال فاتورتك القادمة حتى هذا اليوم", + "no_themes": "يبدو أنه ليس لديك أي سمات بعد، لكن لا تقلق، انقر هنا لإنشاء أول تحفة فنية لك.", + "editor_tab_code": "الكود", + "editor_tab_info": "معلومات", + "editor_tab_save": "حفظ", + "web_store": "المتجر الإلكتروني", + "clear_themes": "مسح", + "create_theme": "إنشاء", + "create_theme_modal_title": "إنشاء سمة مخصصة", + "create_theme_modal_description": "إنشاء سمة جديدة لتخصيص مظهر Hydra", + "theme_name": "الاسم", + "insert_theme_name": "أدخل اسم السمة", + "set_theme": "تعيين السمة", + "unset_theme": "إلغاء تعيين السمة", + "delete_theme": "حذف السمة", + "edit_theme": "تعديل السمة", + "delete_all_themes": "حذف جميع السمات", + "delete_all_themes_description": "سيؤدي هذا إلى حذف جميع السمات المخصصة الخاصة بك", + "delete_theme_description": "سيؤدي هذا إلى حذف السمة {{theme}}", + "cancel": "إلغاء", + "appearance": "المظهر", + "enable_torbox": "تفعيل Torbox", + "torbox_description": "TorBox هي خدمة seedbox متميزة تنافس أفضل الخوادم في السوق.", + "torbox_account_linked": "تم ربط حساب TorBox", + "real_debrid_account_linked": "تم ربط حساب Real-Debrid", + "name_min_length": "يجب أن يكون اسم السمة على الأقل 3 أحرف", + "import_theme": "استيراد سمة", + "import_theme_description": "ستقوم باستيراد {{theme}} من متجر السمات", + "error_importing_theme": "خطأ في استيراد السمة", + "theme_imported": "تم استيراد السمة بنجاح" }, "notifications": { "download_complete": "اكتمل التنزيل", "game_ready_to_install": "{{title}} جاهز للتثبيت", - "repack_list_updated": "تم تحديث قائمة الحزم المعاد تعبئتها", - "repack_count_one": "تمت إضافة {{count}} حزمة معاد تعبئتها", - "repack_count_other": "تمت إضافة {{count}} حزم معاد تعبئتها", + "repack_list_updated": "تم تحديث قائمة الإصدارات المعادة", + "repack_count_one": "تمت إضافة {{count}} إصدار معاد", + "repack_count_other": "تمت إضافة {{count}} إصدارات معادة", "new_update_available": "الإصدار {{version}} متوفر", "restart_to_install_update": "أعد تشغيل Hydra لتثبيت التحديث", "notification_achievement_unlocked_title": "تم فتح إنجاز لـ {{game}}", - "notification_achievement_unlocked_body": "{{achievement}} و {{count}} آخرين تم فتحهم" + "notification_achievement_unlocked_body": "{{achievement}} و {{count}} أخرى تم فتحها" }, "system_tray": { "open": "فتح Hydra", @@ -319,7 +355,7 @@ "binary_not_found_modal": { "title": "البرامج غير مثبتة", "description": "لم يتم العثور على ملفات تشغيل Wine أو Lutris على نظامك", - "instructions": "تحقق من الطريقة الصحيحة لتثبيت أي منها على توزيعة لينكس الخاصة بك حتى تعمل اللعبة بشكل طبيعي" + "instructions": "تحقق من الطريقة الصحيحة لتثبيت أي منها على توزيعة Linux الخاصة بك حتى تعمل اللعبة بشكل طبيعي" }, "modal": { "close": "زر الإغلاق" @@ -328,16 +364,16 @@ "toggle_password_visibility": "تبديل رؤية كلمة المرور" }, "user_profile": { - "amount_hours": "{{amount}} ساعات", - "amount_minutes": "{{amount}} دقائق", - "last_time_played": "آخر تشغيل {{period}}", + "amount_hours": "{{amount}} ساعة", + "amount_minutes": "{{amount}} دقيقة", + "last_time_played": "آخر مرة لعب {{period}}", "activity": "النشاط الأخير", "library": "المكتبة", "total_play_time": "إجمالي وقت اللعب", - "no_recent_activity_title": "همم... لا شيء هنا", + "no_recent_activity_title": "لا شيء هنا...", "no_recent_activity_description": "لم تلعب أي ألعاب مؤخرًا. حان الوقت لتغيير ذلك!", "display_name": "اسم العرض", - "saving": "جارٍ الحفظ", + "saving": "جاري الحفظ", "save": "حفظ", "edit_profile": "تعديل الملف الشخصي", "saved_successfully": "تم الحفظ بنجاح", @@ -346,13 +382,13 @@ "cancel": "إلغاء", "successfully_signed_out": "تم تسجيل الخروج بنجاح", "sign_out": "تسجيل الخروج", - "playing_for": "يلعب لمدة {{amount}}", - "sign_out_modal_text": "مكتبتك مرتبطة بحسابك الحالي. عند تسجيل الخروج، لن تكون مكتبتك مرئية بعد الآن، ولن يتم حفظ أي تقدم. هل تتابع تسجيل الخروج؟", + "playing_for": "جاري اللعب لمدة {{amount}}", + "sign_out_modal_text": "مكتبتك مرتبطة بحسابك الحالي. عند تسجيل الخروج، لن تكون مكتبتك مرئية، ولن يتم حفظ أي تقدم. هل تتابع تسجيل الخروج؟", "add_friends": "إضافة أصدقاء", "add": "إضافة", "friend_code": "رمز الصديق", "see_profile": "عرض الملف الشخصي", - "sending": "جارٍ الإرسال", + "sending": "جاري الإرسال", "friend_request_sent": "تم إرسال طلب الصداقة", "friends": "الأصدقاء", "friends_list": "قائمة الأصدقاء", @@ -371,19 +407,19 @@ "blocked_users": "المستخدمون المحظورون", "unblock": "إلغاء الحظر", "no_friends_added": "ليس لديك أصدقاء مضافون", - "pending": "قيد الانتظار", + "pending": "معلق", "no_pending_invites": "ليس لديك دعوات معلقة", "no_blocked_users": "ليس لديك مستخدمون محظورون", "friend_code_copied": "تم نسخ رمز الصديق", "undo_friendship_modal_text": "سيؤدي هذا إلى إلغاء صداقتك مع {{displayName}}", "privacy_hint": "لضبط من يمكنه رؤية هذا، انتقل إلى <0>الإعدادات", "locked_profile": "هذا الملف الشخصي خاص", - "image_process_failure": "فشل معالجة الصورة", + "image_process_failure": "فشل في معالجة الصورة", "required_field": "هذا الحقل مطلوب", "displayname_min_length": "يجب أن يكون اسم العرض على الأقل 3 أحرف", - "displayname_max_length": "يجب ألا يتجاوز اسم العرض 50 حرفًا", - "report_profile": "الإبلاغ عن هذا الملف الشخصي", - "report_reason": "لماذا تقوم بالإبلاغ عن هذا الملف الشخصي؟", + "displayname_max_length": "يجب أن لا يتجاوز اسم العرض 50 حرفًا", + "report_profile": "الإبلاغ عن هذا الملف", + "report_reason": "لماذا تقوم بالإبلاغ عن هذا الملف؟", "report_description": "معلومات إضافية", "report_description_placeholder": "معلومات إضافية", "report": "الإبلاغ", @@ -393,32 +429,32 @@ "report_reason_spam": "بريد عشوائي", "report_reason_other": "أخرى", "profile_reported": "تم الإبلاغ عن الملف الشخصي", - "your_friend_code": "رمز صديقك:", - "upload_banner": "تحميل بانر", - "uploading_banner": "جارٍ تحميل البانر...", + "your_friend_code": "رمز الصديق الخاص بك:", + "upload_banner": "رفع بانر", + "uploading_banner": "جاري رفع البانر...", "background_image_updated": "تم تحديث صورة الخلفية", "stats": "الإحصائيات", - "achievements": "إنجازات", + "achievements": "الإنجازات", "games": "الألعاب", - "top_percentile": "ال{{percentile}}% الأعلى", + "top_percentile": "الأعلى {{percentile}}%", "ranking_updated_weekly": "يتم تحديث التصنيف أسبوعيًا", - "playing": "يلعب {{game}}", + "playing": "جاري لعب {{game}}", "achievements_unlocked": "الإنجازات المفتوحة", "earned_points": "النقاط المكتسبة", - "show_achievements_on_profile": "عرض إنجازاتك على ملفك الشخصي", - "show_points_on_profile": "عرض نقاطك المكتسبة على ملفك الشخصي" + "show_achievements_on_profile": "عرض إنجازاتك في ملفك الشخصي", + "show_points_on_profile": "عرض نقاطك المكتسبة في ملفك الشخصي" }, "achievement": { "achievement_unlocked": "تم فتح الإنجاز", "user_achievements": "إنجازات {{displayName}}", "your_achievements": "إنجازاتك", "unlocked_at": "تم الفتح في: {{date}}", - "subscription_needed": "يحتاج إلى اشتراك Hydra Cloud لرؤية هذا المحتوى", + "subscription_needed": "يحتاج إلى اشتراك Hydra Cloud لعرض هذا المحتوى", "new_achievements_unlocked": "تم فتح {{achievementCount}} إنجازات جديدة من {{gameCount}} ألعاب", "achievement_progress": "{{unlockedCount}}/{{totalCount}} إنجازات", "achievements_unlocked_for_game": "تم فتح {{achievementCount}} إنجازات جديدة لـ {{gameTitle}}", "hidden_achievement_tooltip": "هذا إنجاز مخفي", - "achievement_earn_points": "اكسب {{points}} نقطة مع هذا الإنجاز", + "achievement_earn_points": "احصل على {{points}} نقاط مع هذا الإنجاز", "earned_points": "النقاط المكتسبة:", "available_points": "النقاط المتاحة:", "how_to_earn_achievements_points": "كيفية كسب نقاط الإنجازات؟" @@ -428,10 +464,10 @@ "subscribe_now": "اشترك الآن", "cloud_saving": "حفظ سحابي", "cloud_achievements": "احفظ إنجازاتك على السحابة", - "animated_profile_picture": "صورة ملف شخصي متحركة", + "animated_profile_picture": "صورة ملف متحركة", "premium_support": "دعم ممتاز", "show_and_compare_achievements": "اعرض وقارن إنجازاتك مع المستخدمين الآخرين", - "animated_profile_banner": "بانر ملف شخصي متحرك", + "animated_profile_banner": "بانر ملف متحرك", "hydra_cloud": "Hydra Cloud", "hydra_cloud_feature_found": "لقد اكتشفت ميزة Hydra Cloud!", "learn_more": "معرفة المزيد" From 0b4c3a6cd2feea5912fc141ebec031040d8237eb Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Mon, 17 Feb 2025 18:59:57 -0300 Subject: [PATCH 269/291] fix: make title bar overlay fully transparent --- src/main/services/window-manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index f51d0e39..dde51b5f 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -57,7 +57,7 @@ export class WindowManager { trafficLightPosition: { x: 16, y: 16 }, titleBarOverlay: { symbolColor: "#DADBE1", - color: "#151515", + color: "#00000000", height: 34, }, webPreferences: { From c5206c68eeb16b764978a1cbd915835b94e548d5 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Mon, 17 Feb 2025 20:06:39 -0300 Subject: [PATCH 270/291] refactor: simplify game details hero section layout and scrolling --- .../game-details/game-details-content.tsx | 51 ++++--------------- .../pages/game-details/hero/hero-panel.tsx | 8 +-- 2 files changed, 12 insertions(+), 47 deletions(-) diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index b2d1334a..e0cd168e 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -16,13 +16,8 @@ import { useUserDetails } from "@renderer/hooks"; import { useSubscription } from "@renderer/hooks/use-subscription"; import "./game-details.scss"; -const HERO_HEIGHT = 300; -const HERO_ANIMATION_THRESHOLD = 25; - export function GameDetailsContent() { const heroRef = useRef(null); - const containerRef = useRef(null); - const [isHeaderStuck, setIsHeaderStuck] = useState(false); const { t } = useTranslation("game_details"); @@ -61,7 +56,7 @@ export function GameDetailsContent() { return t("no_shop_details"); }, [shopDetails, t]); - const [backdropOpactiy, setBackdropOpacity] = useState(1); + const [backdropOpacity, setBackdropOpacity] = useState(1); const handleHeroLoad = async () => { const output = await average(steamUrlBuilder.libraryHero(objectId!), { @@ -80,26 +75,6 @@ export function GameDetailsContent() { setBackdropOpacity(1); }, [objectId]); - const onScroll: React.UIEventHandler = (event) => { - const heroHeight = heroRef.current?.clientHeight ?? HERO_HEIGHT; - - const scrollY = (event.target as HTMLDivElement).scrollTop; - const opacity = Math.max( - 0, - 1 - scrollY / (heroHeight - HERO_ANIMATION_THRESHOLD) - ); - - if (scrollY >= heroHeight && !isHeaderStuck) { - setIsHeaderStuck(true); - } - - if (scrollY <= heroHeight && isHeaderStuck) { - setIsHeaderStuck(false); - } - - setBackdropOpacity(opacity); - }; - const handleCloudSaveButtonClick = () => { if (!userDetails) { window.electron.openAuthWindow(AuthPage.SignIn); @@ -122,31 +97,25 @@ export function GameDetailsContent() {
- {game?.title} - -
+
+ {game?.title}
- +
diff --git a/src/renderer/src/pages/game-details/hero/hero-panel.tsx b/src/renderer/src/pages/game-details/hero/hero-panel.tsx index e9d110f0..8bf8f4b1 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel.tsx +++ b/src/renderer/src/pages/game-details/hero/hero-panel.tsx @@ -9,11 +9,7 @@ import { HeroPanelPlaytime } from "./hero-panel-playtime"; import { gameDetailsContext } from "@renderer/context"; import "./hero-panel.scss"; -export interface HeroPanelProps { - isHeaderStuck: boolean; -} - -export function HeroPanel({ isHeaderStuck }: HeroPanelProps) { +export function HeroPanel() { const { t } = useTranslation("game_details"); const { formatDate } = useDate(); @@ -56,7 +52,7 @@ export function HeroPanel({ isHeaderStuck }: HeroPanelProps) { return (
{getInfo()}
From 75a44bed3fb0c84e6dfac0f1f4cca0f385f60829 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Mon, 17 Feb 2025 20:07:16 -0300 Subject: [PATCH 271/291] refactor: minor hero panel formatting cleanup --- src/renderer/src/pages/game-details/hero/hero-panel.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/renderer/src/pages/game-details/hero/hero-panel.tsx b/src/renderer/src/pages/game-details/hero/hero-panel.tsx index 8bf8f4b1..3a07daa1 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel.tsx +++ b/src/renderer/src/pages/game-details/hero/hero-panel.tsx @@ -50,10 +50,7 @@ export function HeroPanel() { game?.download?.status === "paused"; return ( -
+
{getInfo()}
From cf202c8f01db028c63950e241aebe4737e2aff72 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 17 Feb 2025 21:04:33 -0300 Subject: [PATCH 272/291] feat: add user badges --- src/locales/en/translation.json | 3 ++ src/locales/pt-BR/translation.json | 3 ++ .../src/assets/icons/badge-theme-creator.svg | 29 ++++++++++++++ .../user-profile/user-profile.context.tsx | 2 +- .../profile/profile-hero/profile-hero.scss | 12 ++++++ .../profile/profile-hero/profile-hero.tsx | 10 +++-- .../profile/profile-hero/user-badges.tsx | 39 +++++++++++++++++++ src/types/index.ts | 3 ++ 8 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 src/renderer/src/assets/icons/badge-theme-creator.svg create mode 100644 src/renderer/src/pages/profile/profile-hero/user-badges.tsx diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index f1e85019..bf91cc40 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -444,6 +444,9 @@ "show_achievements_on_profile": "Show your achievements on your profile", "show_points_on_profile": "Show your earned points on your profile" }, + "badge": { + "badge_description_theme_creator": "Awarded to those who created a custom theme" + }, "achievement": { "achievement_unlocked": "Achievement unlocked", "user_achievements": "{{displayName}}'s Achievements", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 0cefd188..3556dbd1 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -440,6 +440,9 @@ "show_achievements_on_profile": "Exiba suas conquistas no perfil", "show_points_on_profile": "Exiba seus pontos ganhos no perfil" }, + "badge": { + "badge_description_theme_creator": "Concedido àqueles que criaram um tema customizado" + }, "achievement": { "achievement_unlocked": "Conquista desbloqueada", "your_achievements": "Suas Conquistas", diff --git a/src/renderer/src/assets/icons/badge-theme-creator.svg b/src/renderer/src/assets/icons/badge-theme-creator.svg new file mode 100644 index 00000000..0793a7e9 --- /dev/null +++ b/src/renderer/src/assets/icons/badge-theme-creator.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/renderer/src/context/user-profile/user-profile.context.tsx b/src/renderer/src/context/user-profile/user-profile.context.tsx index 98a25a77..ce831981 100644 --- a/src/renderer/src/context/user-profile/user-profile.context.tsx +++ b/src/renderer/src/context/user-profile/user-profile.context.tsx @@ -41,7 +41,7 @@ export interface UserProfileContextProviderProps { export function UserProfileContextProvider({ children, userId, -}: UserProfileContextProviderProps) { +}: Readonly) { const { userDetails } = useAppSelector((state) => state.userDetails); const [userStats, setUserStats] = useState(null); diff --git a/src/renderer/src/pages/profile/profile-hero/profile-hero.scss b/src/renderer/src/pages/profile/profile-hero/profile-hero.scss index 8be99794..284d1abf 100644 --- a/src/renderer/src/pages/profile/profile-hero/profile-hero.scss +++ b/src/renderer/src/pages/profile/profile-hero/profile-hero.scss @@ -65,6 +65,12 @@ overflow: hidden; } + &__display-name-container { + display: flex; + gap: globals.$spacing-unit; + align-items: center; + } + &__display-name { font-weight: bold; overflow: hidden; @@ -76,6 +82,12 @@ text-shadow: 0 0 5px rgb(0 0 0 / 40%); } + &__display-name-badges { + display: flex; + gap: globals.$spacing-unit; + align-items: center; + } + &__current-game { &-wrapper { display: flex; diff --git a/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx b/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx index e198fbf2..66799c47 100644 --- a/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx +++ b/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx @@ -25,6 +25,7 @@ import { EditProfileModal } from "../edit-profile-modal/edit-profile-modal"; import Skeleton from "react-loading-skeleton"; import { UploadBackgroundImageButton } from "../upload-background-image-button/upload-background-image-button"; import "./profile-hero.scss"; +import { UserBadges } from "./user-badges"; type FriendAction = | FriendRequestAction @@ -307,9 +308,12 @@ export function ProfileHero() {
{userProfile ? ( -

- {userProfile?.displayName} -

+
+

+ {userProfile?.displayName} +

+ +
) : ( )} diff --git a/src/renderer/src/pages/profile/profile-hero/user-badges.tsx b/src/renderer/src/pages/profile/profile-hero/user-badges.tsx new file mode 100644 index 00000000..4ca180a0 --- /dev/null +++ b/src/renderer/src/pages/profile/profile-hero/user-badges.tsx @@ -0,0 +1,39 @@ +import BadgeThemeCreator from "@renderer/assets/icons/badge-theme-creator.svg?react"; +import "./profile-hero.scss"; +import { useContext } from "react"; +import { userProfileContext } from "@renderer/context"; +import { UserBadge } from "@types"; +import { useTranslation } from "react-i18next"; + +export function UserBadges() { + const { t } = useTranslation("badge"); + const { userProfile } = useContext(userProfileContext); + + if (!userProfile?.badges?.length) return null; + + const getBadgeIcon = (badge: UserBadge) => { + if (badge === "THEME_CREATOR") { + return ; + } + + return null; + }; + + return ( +
+ {userProfile.badges.map((badge) => { + const badgeIcon = getBadgeIcon(badge); + + if (!badgeIcon) return null; + return ( +
+ {badgeIcon} +
+ ); + })} +
+ ); +} diff --git a/src/types/index.ts b/src/types/index.ts index 53fb97dc..e17a694a 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -130,6 +130,8 @@ export interface UserProfileCurrentGame extends Omit { export type ProfileVisibility = "PUBLIC" | "PRIVATE" | "FRIENDS"; +export type UserBadge = "THEME_CREATOR"; + export interface UserDetails { id: string; username: string; @@ -164,6 +166,7 @@ export interface UserProfile { quirks: { backupsPerGameLimit: number; }; + badges: UserBadge[]; } export interface UpdateProfileRequest { From e066ea3503e1aaa12f66976bc496260f9eca22ef Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 17 Feb 2025 21:14:32 -0300 Subject: [PATCH 273/291] feat: css --- src/renderer/src/pages/profile/profile-hero/profile-hero.scss | 2 +- src/renderer/src/pages/profile/profile-hero/user-badges.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/profile/profile-hero/profile-hero.scss b/src/renderer/src/pages/profile/profile-hero/profile-hero.scss index 284d1abf..fd21cf1d 100644 --- a/src/renderer/src/pages/profile/profile-hero/profile-hero.scss +++ b/src/renderer/src/pages/profile/profile-hero/profile-hero.scss @@ -82,7 +82,7 @@ text-shadow: 0 0 5px rgb(0 0 0 / 40%); } - &__display-name-badges { + &__display-name-badges-container { display: flex; gap: globals.$spacing-unit; align-items: center; diff --git a/src/renderer/src/pages/profile/profile-hero/user-badges.tsx b/src/renderer/src/pages/profile/profile-hero/user-badges.tsx index 4ca180a0..ed5d0571 100644 --- a/src/renderer/src/pages/profile/profile-hero/user-badges.tsx +++ b/src/renderer/src/pages/profile/profile-hero/user-badges.tsx @@ -20,13 +20,14 @@ export function UserBadges() { }; return ( -
+
{userProfile.badges.map((badge) => { const badgeIcon = getBadgeIcon(badge); if (!badgeIcon) return null; return (
From 0724a40cb70404789c9ca0cc69a13ddefdf3cc20 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Mon, 17 Feb 2025 22:29:42 -0300 Subject: [PATCH 274/291] fix: improve theme import modal flow and navigation --- .../settings/aparence/settings-appearance.tsx | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx index 625f1a90..047cf299 100644 --- a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx +++ b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx @@ -4,6 +4,7 @@ import { ThemeActions, ThemeCard, ThemePlaceholder } from "./index"; import type { Theme } from "@types"; import { ImportThemeModal } from "./modals/import-theme-modal"; import { settingsContext } from "@renderer/context"; +import { useNavigate } from "react-router-dom"; interface SettingsAppearanceProps { appearance: { @@ -24,8 +25,10 @@ export function SettingsAppearance({ authorId: string; authorName: string; } | null>(null); + const [hasShownModal, setHasShownModal] = useState(false); const { clearTheme } = useContext(settingsContext); + const navigate = useNavigate(); const loadThemes = useCallback(async () => { const themesList = await window.electron.getAllCustomThemes(); @@ -45,20 +48,32 @@ export function SettingsAppearance({ }, [loadThemes]); useEffect(() => { - if (appearance.theme && appearance.authorId && appearance.authorName) { + if (appearance.theme && appearance.authorId && appearance.authorName && !hasShownModal) { setIsImportThemeModalVisible(true); setImportTheme({ theme: appearance.theme, authorId: appearance.authorId, authorName: appearance.authorName, }); + setHasShownModal(true); + + navigate("/settings", { replace: true }); + clearTheme(); } - }, [appearance.theme, appearance.authorId, appearance.authorName]); + }, [ + appearance.theme, + appearance.authorId, + appearance.authorName, + navigate, + hasShownModal, + clearTheme, + ]); const onThemeImported = useCallback(() => { setIsImportThemeModalVisible(false); + setImportTheme(null); loadThemes(); - }, [clearTheme, loadThemes]); + }, [loadThemes]); return (
From 21cec50e2e358e1bc8c8df51ed23343318ee156c Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Mon, 17 Feb 2025 22:32:07 -0300 Subject: [PATCH 275/291] lint --- .../src/pages/settings/aparence/settings-appearance.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx index 047cf299..2cc5bec1 100644 --- a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx +++ b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx @@ -48,7 +48,12 @@ export function SettingsAppearance({ }, [loadThemes]); useEffect(() => { - if (appearance.theme && appearance.authorId && appearance.authorName && !hasShownModal) { + if ( + appearance.theme && + appearance.authorId && + appearance.authorName && + !hasShownModal + ) { setIsImportThemeModalVisible(true); setImportTheme({ theme: appearance.theme, From 726a39a430761af9b16f910c4ec6f6b91ab74d5c Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 17 Feb 2025 22:40:20 -0300 Subject: [PATCH 276/291] chore: bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fd7c2855..f15861b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydralauncher", - "version": "3.2.0", + "version": "3.2.1", "description": "Hydra", "main": "./out/main/index.js", "author": "Los Broxas", From 73ab3872a733495772f765bac9a57cde2ad87aa1 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Tue, 18 Feb 2025 22:25:41 -0300 Subject: [PATCH 277/291] fix: lazy loading messing up custom css --- src/renderer/src/app.tsx | 4 +- src/renderer/src/main.tsx | 62 +++++-------------- .../settings/aparence/settings-appearance.tsx | 1 + 3 files changed, 18 insertions(+), 49 deletions(-) diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index daa93a6b..0b738c6f 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -263,9 +263,7 @@ export function App() { useEffect(() => { const unsubscribe = window.electron.onCssInjected((cssString) => { - if (cssString) { - injectCustomCss(cssString); - } + injectCustomCss(cssString); }); return () => unsubscribe(); diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index 13160aeb..eb890d18 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -18,26 +18,17 @@ import { store } from "./store"; import resources from "@locales"; -import { SuspenseWrapper } from "./components"; import { logger } from "./logger"; import { addCookieInterceptor } from "./cookies"; - -const Home = React.lazy(() => import("./pages/home/home")); -const GameDetails = React.lazy( - () => import("./pages/game-details/game-details") -); -const Downloads = React.lazy(() => import("./pages/downloads/downloads")); -const Settings = React.lazy(() => import("./pages/settings/settings")); -const Catalogue = React.lazy(() => import("./pages/catalogue/catalogue")); -const Profile = React.lazy(() => import("./pages/profile/profile")); -const Achievements = React.lazy( - () => import("./pages/achievements/achievements") -); -const ThemeEditor = React.lazy( - () => import("./pages/theme-editor/theme-editor") -); - import * as Sentry from "@sentry/react"; +import Catalogue from "./pages/catalogue/catalogue"; +import Home from "./pages/home/home"; +import Downloads from "./pages/downloads/downloads"; +import GameDetails from "./pages/game-details/game-details"; +import Settings from "./pages/settings/settings"; +import Profile from "./pages/profile/profile"; +import Achievements from "./pages/achievements/achievements"; +import ThemeEditor from "./pages/theme-editor/theme-editor"; Sentry.init({ dsn: import.meta.env.RENDERER_VITE_SENTRY_DSN, @@ -82,37 +73,16 @@ ReactDOM.createRoot(document.getElementById("root")!).render( }> - } /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> - } - /> + } /> diff --git a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx index 2cc5bec1..b8ab0b84 100644 --- a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx +++ b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx @@ -110,6 +110,7 @@ export function SettingsAppearance({ onClose={() => { setIsImportThemeModalVisible(false); clearTheme(); + setHasShownModal(false); }} onThemeImported={onThemeImported} themeName={importTheme.theme} From d879f2e3df6bf228c2aef0f7b611f04be484f1a6 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Tue, 18 Feb 2025 22:29:38 -0300 Subject: [PATCH 278/291] chore: bump version --- .github/workflows/build.yml | 3 +++ .github/workflows/release.yml | 3 +++ package.json | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 161708bb..e95b1329 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,8 @@ name: Build +concurrency: + group: build + on: pull_request jobs: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index afa5502c..1d960908 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,8 @@ name: Release +concurrency: + group: release + on: push: branches: main diff --git a/package.json b/package.json index f15861b2..136f3e2f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydralauncher", - "version": "3.2.1", + "version": "3.2.2", "description": "Hydra", "main": "./out/main/index.js", "author": "Los Broxas", From 58855a93a80e5a1ee966eb3d53cd00de291bc14a Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Tue, 18 Feb 2025 22:31:19 -0300 Subject: [PATCH 279/291] feat: add readonly to test pipeline --- src/renderer/src/components/backdrop/backdrop.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/components/backdrop/backdrop.tsx b/src/renderer/src/components/backdrop/backdrop.tsx index e62d42ee..5caedb0e 100644 --- a/src/renderer/src/components/backdrop/backdrop.tsx +++ b/src/renderer/src/components/backdrop/backdrop.tsx @@ -6,7 +6,10 @@ export interface BackdropProps { children: React.ReactNode; } -export function Backdrop({ isClosing = false, children }: BackdropProps) { +export function Backdrop({ + isClosing = false, + children, +}: Readonly) { return (
Date: Tue, 18 Feb 2025 22:32:13 -0300 Subject: [PATCH 280/291] feat: add readonly to test pipeline --- src/renderer/src/components/button/button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/components/button/button.tsx b/src/renderer/src/components/button/button.tsx index 8d8bf1dd..fd86d4b2 100644 --- a/src/renderer/src/components/button/button.tsx +++ b/src/renderer/src/components/button/button.tsx @@ -15,7 +15,7 @@ export function Button({ theme = "primary", className, ...props -}: ButtonProps) { +}: Readonly) { return (