Compare commits

..

18 Commits

Author SHA1 Message Date
Chubby Granny Chaser
7f28fc8ca1 Merge pull request #1893 from hydralauncher/fix/downloads-ui
Some checks failed
Build / build (ubuntu-latest) (push) Has been cancelled
Build / build (windows-2022) (push) Has been cancelled
Build Renderer / build (push) Has been cancelled
Release / build (ubuntu-latest) (push) Has been cancelled
Release / build (windows-2022) (push) Has been cancelled
fix: navigation on game image click not working
2025-12-11 00:53:38 +00:00
Moyase
d1eb174429 Merge branch 'main' into fix/downloads-ui 2025-12-10 20:38:07 +02:00
Moyasee
82a125237b fix: navigation on game image click not working 2025-12-10 20:36:24 +02:00
Chubby Granny Chaser
19e312d31e Merge pull request #1891 from hydralauncher/fix/LBX-298
Some checks failed
Build / build (ubuntu-latest) (push) Has been cancelled
Build / build (windows-2022) (push) Has been cancelled
refactor: simplify Aria2 spawn logic and update GofileApi download li…
2025-12-10 18:12:33 +00:00
Chubby Granny Chaser
79b1f05cde Merge branch 'main' into fix/LBX-298 2025-12-10 18:12:17 +00:00
Chubby Granny Chaser
cc9ac9dc0f Merge pull request #1892 from hydralauncher/fix/downloads-ui
Fix: navigation and styles in download page
2025-12-10 18:12:05 +00:00
Moyasee
19406dd051 style(download-group): remove unnecessary blank line for cleaner SCSS 2025-12-10 19:54:22 +02:00
Moyasee
8aa6e113e7 refactor(download-group): update button interaction and styles 2025-12-10 19:53:53 +02:00
Chubby Granny Chaser
91ad4a68f7 Merge branch 'main' into fix/LBX-298 2025-12-10 17:18:49 +00:00
Chubby Granny Chaser
a69a6ec510 Merge pull request #1889 from Lianela/main
feat: new strings
2025-12-10 17:15:45 +00:00
Chubby Granny Chaser
fada6507c3 Merge branch 'main' into main 2025-12-10 17:15:21 +00:00
Chubby Granny Chaser
0479f1347b Merge pull request #1887 from hydralauncher/dependabot/npm_and_yarn/npm_and_yarn-a3f223628e
chore(deps): bump jws from 3.2.2 to 3.2.3 in the npm_and_yarn group across 1 directory
2025-12-10 17:14:44 +00:00
Chubby Granny Chaser
817870cdbb refactor: simplify Aria2 spawn logic and update GofileApi download link request 2025-12-10 17:11:10 +00:00
dependabot[bot]
f44d5c8b49 chore(deps): bump jws in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [jws](https://github.com/brianloveswords/node-jws).


Updates `jws` from 3.2.2 to 3.2.3
- [Release notes](https://github.com/brianloveswords/node-jws/releases)
- [Changelog](https://github.com/auth0/node-jws/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianloveswords/node-jws/compare/v3.2.2...v3.2.3)

---
updated-dependencies:
- dependency-name: jws
  dependency-version: 3.2.3
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 01:04:55 +00:00
Zamitto
c36109c092 chore: bump version
Some checks failed
Build / build (ubuntu-latest) (push) Has been cancelled
Build / build (windows-2022) (push) Has been cancelled
2025-12-07 22:03:02 -03:00
Zamitto
b59fb7dc36 feat: support workwonders 2025-12-07 20:38:53 -03:00
Kyatto
214a7af408 Fix JSON formatting in translation file 2025-12-07 13:14:50 -06:00
Kyatto
14679fc31e Add new translation strings in Spanish 2025-12-07 13:05:59 -06:00
21 changed files with 241 additions and 359 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "hydralauncher", "name": "hydralauncher",
"version": "3.7.5", "version": "3.7.6",
"description": "Hydra", "description": "Hydra",
"main": "./out/main/index.js", "main": "./out/main/index.js",
"author": "Los Broxas", "author": "Los Broxas",
@@ -19,12 +19,12 @@
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix", "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false", "typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false", "typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
"typecheck": "yarn run typecheck:node && yarn run typecheck:web", "typecheck": "npm run typecheck:node && npm run typecheck:web",
"start": "electron-vite preview", "start": "electron-vite preview",
"dev": "electron-vite dev", "dev": "electron-vite dev",
"build": "yarn run typecheck && electron-vite build", "build": "npm run typecheck && electron-vite build",
"postinstall": "electron-builder install-app-deps && node ./scripts/postinstall.cjs", "postinstall": "electron-builder install-app-deps && node ./scripts/postinstall.cjs",
"build:unpack": "yarn run build && electron-builder --dir", "build:unpack": "npm run build && electron-builder --dir",
"build:win": "electron-vite build && electron-builder --win", "build:win": "electron-vite build && electron-builder --win",
"build:mac": "electron-vite build && electron-builder --mac", "build:mac": "electron-vite build && electron-builder --mac",
"build:linux": "electron-vite build && electron-builder --linux", "build:linux": "electron-vite build && electron-builder --linux",

View File

@@ -16,7 +16,6 @@
"library": "Library", "library": "Library",
"downloads": "Downloads", "downloads": "Downloads",
"settings": "Settings", "settings": "Settings",
"hydra_2025_wrapped": "Hydra Wrapped 2025 Available",
"my_library": "My library", "my_library": "My library",
"downloading_metadata": "{{title}} (Downloading metadata…)", "downloading_metadata": "{{title}} (Downloading metadata…)",
"paused": "{{title}} (Paused)", "paused": "{{title}} (Paused)",
@@ -415,11 +414,7 @@
"resume_seeding": "Resume seeding", "resume_seeding": "Resume seeding",
"options": "Manage", "options": "Manage",
"extract": "Extract files", "extract": "Extract files",
"extracting": "Extracting files…", "extracting": "Extracting files…"
"network": "Network",
"peak": "Peak",
"seeds": "Seeds",
"peers": "Peers"
}, },
"settings": { "settings": {
"downloads_path": "Downloads path", "downloads_path": "Downloads path",

View File

@@ -16,7 +16,6 @@
"library": "Librería", "library": "Librería",
"downloads": "Descargas", "downloads": "Descargas",
"settings": "Ajustes", "settings": "Ajustes",
"hydra_2025_wrapped": "Hydra Wrapped 2025 Disponible",
"my_library": "Mi Librería", "my_library": "Mi Librería",
"downloading_metadata": "{{title}} (Descargando metadatos…)", "downloading_metadata": "{{title}} (Descargando metadatos…)",
"paused": "{{title}} (Pausado)", "paused": "{{title}} (Pausado)",
@@ -415,11 +414,7 @@
"resume_seeding": "Continuar sembrando", "resume_seeding": "Continuar sembrando",
"options": "Administrar", "options": "Administrar",
"extract": "Extraer archivos", "extract": "Extraer archivos",
"extracting": "Extrayendo archivos…", "extracting": "Extrayendo archivos…"
"network": "Red",
"peak": "Pico",
"seeds": "Seeds",
"peers": "Peers"
}, },
"settings": { "settings": {
"downloads_path": "Ruta de descarga", "downloads_path": "Ruta de descarga",
@@ -463,6 +458,7 @@
"description_confirmation_delete_all_sources": "Vas a eliminar todas las fuentes de descargas", "description_confirmation_delete_all_sources": "Vas a eliminar todas las fuentes de descargas",
"button_delete_all_sources": "Eliminar todo", "button_delete_all_sources": "Eliminar todo",
"added_download_source": "Añadir fuente de descarga", "added_download_source": "Añadir fuente de descarga",
"adding": "Añadiendo…",
"download_sources_synced": "Todas las fuentes de descarga están sincronizadas", "download_sources_synced": "Todas las fuentes de descarga están sincronizadas",
"insert_valid_json_url": "Introducí una URL de json válida", "insert_valid_json_url": "Introducí una URL de json válida",
"found_download_option_zero": "Sin opciones de descargas encontrada", "found_download_option_zero": "Sin opciones de descargas encontrada",
@@ -568,6 +564,19 @@
"debrid_description": "Los servicios Debrid son descargadores premium sin restricciones que te dejan descargar más rápido archivos alojados en servicios de alojamiento siendo que la única limitación es tu velocidad de internet.", "debrid_description": "Los servicios Debrid son descargadores premium sin restricciones que te dejan descargar más rápido archivos alojados en servicios de alojamiento siendo que la única limitación es tu velocidad de internet.",
"enable_friend_start_game_notifications": "Cuando un amigo está jugando un juego", "enable_friend_start_game_notifications": "Cuando un amigo está jugando un juego",
"autoplay_trailers_on_game_page": "Reproducir trailers automáticamente en la página del juego", "autoplay_trailers_on_game_page": "Reproducir trailers automáticamente en la página del juego",
"change_achievement_sound": "Cambiar sonido de logro",
"download_source_already_exists": "Esta fuente de descarga URL ya existe.",
"download_source_failed": "Error",
"download_source_matched": "Actualizado",
"download_source_matching": "Actualizando",
"download_source_no_information": "Sin información disponible",
"download_source_pending_matching": "Actualizando pronto",
"download_sources_synced_successfully": "Todas las fuentes de descarga están sincronizadas",
"failed_add_download_source": "Error al añadir la fuente de descarga. Por favor intentá de nuevo.",
"hydra_cloud": "Hydra Cloud",
"preview_sound": "Vista previa de sonido",
"remove_achievement_sound": "Eliminar sonido de logros",
"removed_all_download_sources": "Todas las fuentes de descarga eliminadas",
"hide_to_tray_on_game_start": "Ocultar Hydra en la bandeja al iniciar un juego" "hide_to_tray_on_game_start": "Ocultar Hydra en la bandeja al iniciar un juego"
}, },
"notifications": { "notifications": {

View File

@@ -16,7 +16,6 @@
"library": "Biblioteca", "library": "Biblioteca",
"downloads": "Downloads", "downloads": "Downloads",
"settings": "Ajustes", "settings": "Ajustes",
"hydra_2025_wrapped": "Hydra Wrapped 2025 Já disponível",
"my_library": "Biblioteca", "my_library": "Biblioteca",
"downloading_metadata": "{{title}} (Baixando metadados…)", "downloading_metadata": "{{title}} (Baixando metadados…)",
"paused": "{{title}} (Pausado)", "paused": "{{title}} (Pausado)",
@@ -403,11 +402,7 @@
"resume_seeding": "Semear", "resume_seeding": "Semear",
"options": "Gerenciar", "options": "Gerenciar",
"extract": "Extrair arquivos", "extract": "Extrair arquivos",
"extracting": "Extraindo arquivos…", "extracting": "Extraindo arquivos…"
"network": "Rede",
"peak": "Pico",
"seeds": "Seeds",
"peers": "Peers"
}, },
"settings": { "settings": {
"downloads_path": "Diretório dos downloads", "downloads_path": "Diretório dos downloads",

View File

@@ -15,7 +15,6 @@
"catalogue": "Catálogo", "catalogue": "Catálogo",
"downloads": "Transferências", "downloads": "Transferências",
"settings": "Definições", "settings": "Definições",
"hydra_2025_wrapped": "Hydra Wrapped 2025 Já disponível",
"my_library": "Biblioteca", "my_library": "Biblioteca",
"downloading_metadata": "{{title}} (A transferir metadados…)", "downloading_metadata": "{{title}} (A transferir metadados…)",
"paused": "{{title}} (Em pausa)", "paused": "{{title}} (Em pausa)",
@@ -230,13 +229,7 @@
"seeding": "A semear", "seeding": "A semear",
"stop_seeding": "Parar de semear", "stop_seeding": "Parar de semear",
"resume_seeding": "Semear", "resume_seeding": "Semear",
"options": "Opções", "options": "Opções"
"extract": "Extrair ficheiros",
"extracting": "A extrair ficheiros…",
"network": "Rede",
"peak": "Pico",
"seeds": "Seeds",
"peers": "Peers"
}, },
"settings": { "settings": {
"downloads_path": "Local das transferências", "downloads_path": "Local das transferências",

View File

@@ -16,7 +16,6 @@
"library": "Библиотека", "library": "Библиотека",
"downloads": "Загрузки", "downloads": "Загрузки",
"settings": "Настройки", "settings": "Настройки",
"hydra_2025_wrapped": "Hydra Wrapped 2025 Доступно",
"my_library": "Библиотека", "my_library": "Библиотека",
"downloading_metadata": "{{title}} (Загрузка метаданных…)", "downloading_metadata": "{{title}} (Загрузка метаданных…)",
"paused": "{{title}} (Приостановлено)", "paused": "{{title}} (Приостановлено)",
@@ -415,11 +414,7 @@
"resume_seeding": "Продолжить раздачу", "resume_seeding": "Продолжить раздачу",
"options": "Управлять", "options": "Управлять",
"extract": "Распаковать файлы", "extract": "Распаковать файлы",
"extracting": "Распаковка файлов…", "extracting": "Распаковка файлов…"
"network": "Сеть",
"peak": "Пик",
"seeds": "Seeds",
"peers": "Peers"
}, },
"settings": { "settings": {
"downloads_path": "Путь загрузок", "downloads_path": "Путь загрузок",

View File

@@ -33,9 +33,7 @@ export const loadState = async () => {
await import("./events"); await import("./events");
if (process.platform !== "darwin") { Aria2.spawn();
Aria2.spawn();
}
if (userPreferences?.realDebridApiToken) { if (userPreferences?.realDebridApiToken) {
RealDebridClient.authorize(userPreferences.realDebridApiToken); RealDebridClient.authorize(userPreferences.realDebridApiToken);

View File

@@ -7,9 +7,12 @@ export class Aria2 {
private static process: cp.ChildProcess | null = null; private static process: cp.ChildProcess | null = null;
public static spawn() { public static spawn() {
const binaryPath = app.isPackaged const binaryPath =
? path.join(process.resourcesPath, "aria2c") process.platform === "darwin"
: path.join(__dirname, "..", "..", "binaries", "aria2c"); ? "aria2c"
: app.isPackaged
? path.join(process.resourcesPath, "aria2c")
: path.join(__dirname, "..", "..", "binaries", "aria2c");
this.process = cp.spawn( this.process = cp.spawn(
binaryPath, binaryPath,

View File

@@ -36,16 +36,13 @@ export class GofileApi {
} }
public static async getDownloadLink(id: string) { public static async getDownloadLink(id: string) {
const searchParams = new URLSearchParams({
wt: WT,
});
const response = await axios.get<{ const response = await axios.get<{
status: string; status: string;
data: GofileContentsResponse; data: GofileContentsResponse;
}>(`https://api.gofile.io/contents/${id}?${searchParams.toString()}`, { }>(`https://api.gofile.io/contents/${id}`, {
headers: { headers: {
Authorization: `Bearer ${this.token}`, Authorization: `Bearer ${this.token}`,
"X-Website-Token": WT,
}, },
}); });

View File

@@ -58,7 +58,13 @@ export class HydraApi {
const decodedBase64 = atob(payload as string); const decodedBase64 = atob(payload as string);
const jsonData = JSON.parse(decodedBase64); const jsonData = JSON.parse(decodedBase64);
const { accessToken, expiresIn, refreshToken } = jsonData; const {
accessToken,
expiresIn,
refreshToken,
featurebaseJwt,
workwondersJwt,
} = jsonData;
const now = new Date(); const now = new Date();
@@ -85,6 +91,8 @@ export class HydraApi {
accessToken, accessToken,
refreshToken, refreshToken,
tokenExpirationTimestamp, tokenExpirationTimestamp,
featurebaseJwt,
workwondersJwt,
}, },
{ valueEncoding: "json" } { valueEncoding: "json" }
); );

View File

@@ -138,7 +138,8 @@ export class WindowManager {
(details, callback) => { (details, callback) => {
if ( if (
details.webContentsId !== this.mainWindow?.webContents.id || details.webContentsId !== this.mainWindow?.webContents.id ||
details.url.includes("chatwoot") details.url.includes("chatwoot") ||
details.url.includes("workwonders")
) { ) {
return callback(details); return callback(details);
} }
@@ -159,7 +160,8 @@ export class WindowManager {
if ( if (
details.webContentsId !== this.mainWindow?.webContents.id || details.webContentsId !== this.mainWindow?.webContents.id ||
details.url.includes("featurebase") || details.url.includes("featurebase") ||
details.url.includes("chatwoot") details.url.includes("chatwoot") ||
details.url.includes("workwonders")
) { ) {
return callback(details); return callback(details);
} }
@@ -354,8 +356,6 @@ export class WindowManager {
public static async createNotificationWindow() { public static async createNotificationWindow() {
if (this.notificationWindow) return; if (this.notificationWindow) return;
if (process.platform === "darwin") return;
const userPreferences = await db.get<string, UserPreferences | undefined>( const userPreferences = await db.get<string, UserPreferences | undefined>(
levelKeys.userPreferences, levelKeys.userPreferences,
{ {

View File

@@ -122,10 +122,10 @@ export function BottomPanel() {
</button> </button>
<button <button
data-featurebase-changelog data-open-workwonders-changelog-mini
className="bottom-panel__version-button" className="bottom-panel__version-button"
> >
<small data-featurebase-changelog> <small>
{sessionHash ? `${sessionHash} -` : ""} v{version} &quot; {sessionHash ? `${sessionHash} -` : ""} v{version} &quot;
{VERSION_CODENAME}&quot; {VERSION_CODENAME}&quot;
</small> </small>

View File

@@ -32,15 +32,4 @@ export const routes = [
nameKey: "settings", nameKey: "settings",
render: () => <GearIcon />, render: () => <GearIcon />,
}, },
{
path: "https://hydrawrapped.com",
nameKey: "hydra_2025_wrapped",
render: () => (
<img
src="https://cdn.losbroxas.org/thumbnail_hydra_badge2_fb01af31e3.png"
alt="Hydra 2025 Wrapped"
style={{ width: 16, height: 16 }}
/>
),
},
]; ];

View File

@@ -88,34 +88,6 @@
); );
} }
} }
&--wrapped {
background: linear-gradient(
135deg,
rgba(74, 144, 226, 0.25) 0%,
rgba(123, 104, 238, 0.2) 25%,
rgba(59, 130, 246, 0.25) 50%,
rgba(96, 165, 250, 0.2) 75%,
rgba(74, 144, 226, 0.25) 100%
);
background-size: 200% 200%;
animation: wrapped-gradient-flow 8s ease infinite;
color: globals.$muted-color;
position: relative;
overflow: hidden;
&:hover {
background: linear-gradient(
135deg,
rgba(74, 144, 226, 0.35) 0%,
rgba(123, 104, 238, 0.3) 25%,
rgba(59, 130, 246, 0.35) 50%,
rgba(96, 165, 250, 0.3) 75%,
rgba(74, 144, 226, 0.35) 100%
);
background-size: 200% 200%;
}
}
} }
&__menu-item-button { &__menu-item-button {
@@ -134,21 +106,6 @@
overflow: hidden; overflow: hidden;
} }
&__menu-item-marquee {
overflow: hidden;
flex: 1;
min-width: 0;
}
&__menu-item-marquee-content {
display: inline-flex;
}
&__menu-item-marquee-content span {
display: inline-block;
flex-shrink: 0;
}
&__game-icon { &__game-icon {
width: 20px; width: 20px;
height: 20px; height: 20px;
@@ -271,24 +228,3 @@
} }
} }
} }
@keyframes wrapped-gradient-flow {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
@keyframes marquee-scroll {
0% {
transform: translateX(0);
}
100% {
transform: translateX(calc(-50% - 1em));
}
}

View File

@@ -22,8 +22,6 @@ import { buildGameDetailsPath } from "@renderer/helpers";
import { SidebarProfile } from "./sidebar-profile"; import { SidebarProfile } from "./sidebar-profile";
import { sortBy } from "lodash-es"; import { sortBy } from "lodash-es";
import cn from "classnames"; import cn from "classnames";
import { logger } from "@renderer/logger";
import { motion } from "framer-motion";
import { import {
CommentDiscussionIcon, CommentDiscussionIcon,
PlayIcon, PlayIcon,
@@ -240,32 +238,8 @@ export function Sidebar() {
return game.title; return game.title;
}; };
const handleSidebarItemClick = async (path: string) => { const handleSidebarItemClick = (path: string) => {
if (path.startsWith("http")) { if (path !== location.pathname) {
if (path === "https://hydrawrapped.com") {
try {
const auth = await window.electron.getAuth();
if (auth) {
const payload = {
accessToken: auth.accessToken,
refreshToken: auth.refreshToken,
expiresIn: 3600,
};
const base64Payload = btoa(JSON.stringify(payload));
window.electron.openExternal(
`${path}?payload=${encodeURIComponent(base64Payload)}`
);
} else {
window.electron.openExternal(path);
}
} catch (error) {
logger.error("Failed to get auth for wrapped:", error);
window.electron.openExternal(path);
}
} else {
window.electron.openExternal(path);
}
} else if (path !== location.pathname) {
navigate(path); navigate(path);
} }
}; };
@@ -323,10 +297,7 @@ export function Sidebar() {
<li <li
key={nameKey} key={nameKey}
className={cn("sidebar__menu-item", { className={cn("sidebar__menu-item", {
"sidebar__menu-item--active": "sidebar__menu-item--active": location.pathname === path,
!path.startsWith("http") && location.pathname === path,
"sidebar__menu-item--wrapped":
nameKey === "hydra_2025_wrapped",
})} })}
> >
<button <button
@@ -335,33 +306,7 @@ export function Sidebar() {
onClick={() => handleSidebarItemClick(path)} onClick={() => handleSidebarItemClick(path)}
> >
{render()} {render()}
{nameKey === "hydra_2025_wrapped" ? ( <span>{t(nameKey)}</span>
<div className="sidebar__menu-item-marquee">
<motion.div
className="sidebar__menu-item-marquee-content"
animate={{
x: ["0%", "-50%"],
}}
transition={{
x: {
repeat: Infinity,
repeatType: "loop",
duration: 8,
ease: "linear",
},
}}
>
<span>
{t(nameKey)} &nbsp;&nbsp;&nbsp;&nbsp;
</span>
<span>
{t(nameKey)} &nbsp;&nbsp;&nbsp;&nbsp;
</span>
</motion.div>
</div>
) : (
<span>{t(nameKey)}</span>
)}
</button> </button>
</li> </li>
))} ))}

View File

@@ -108,16 +108,11 @@
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
transition: opacity 0.2s ease; transition: scale 0.2s ease;
outline: none; outline: none;
&:hover { &:hover {
opacity: 0.8; scale: 1.05;
}
&:focus,
&:focus-visible {
outline: none;
} }
} }
@@ -395,6 +390,21 @@
flex-shrink: 0; flex-shrink: 0;
background-color: rgba(0, 0, 0, 0.3); background-color: rgba(0, 0, 0, 0.3);
border: 1px solid globals.$border-color; border: 1px solid globals.$border-color;
padding: 0;
cursor: pointer;
transition:
opacity 0.2s ease,
transform 0.2s ease;
&:hover {
opacity: 0.9;
}
&:focus,
&:focus-visible {
outline: 2px solid rgba(255, 255, 255, 0.5);
outline-offset: 2px;
}
img { img {
width: 100%; width: 100%;
@@ -411,6 +421,21 @@
gap: calc(globals.$spacing-unit / 1); gap: calc(globals.$spacing-unit / 1);
} }
&__simple-title-button {
background: none;
border: none;
padding: 0;
cursor: pointer;
text-align: left;
width: 100%;
transition: opacity 0.2s ease;
&:focus,
&:focus-visible {
outline: none;
}
}
&__simple-title { &__simple-title {
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;

View File

@@ -305,11 +305,9 @@ function HeroDownloadView({
)} )}
</span> </span>
)} )}
{(!lastPacket?.isCheckingFiles || currentProgress > 0) && ( <span className="download-group__progress-percentage">
<span className="download-group__progress-percentage"> <AnimatedPercentage value={currentProgress} />
<AnimatedPercentage value={currentProgress} /> </span>
</span>
)}
</div> </div>
<div className="download-group__progress-bar"> <div className="download-group__progress-bar">
<div <div
@@ -360,7 +358,7 @@ function HeroDownloadView({
</span> </span>
<div className="download-group__stat-content"> <div className="download-group__stat-content">
<span className="download-group__stat-label"> <span className="download-group__stat-label">
{t("network")} {t("network")}:
</span> </span>
<span className="download-group__stat-value"> <span className="download-group__stat-value">
{isGameDownloading ? formatSpeed(downloadSpeed) : "0 B/s"} {isGameDownloading ? formatSpeed(downloadSpeed) : "0 B/s"}
@@ -373,38 +371,37 @@ function HeroDownloadView({
<GraphIcon size={16} /> <GraphIcon size={16} />
</span> </span>
<div className="download-group__stat-content"> <div className="download-group__stat-content">
<span className="download-group__stat-label">{t("peak")}</span> <span className="download-group__stat-label">{t("peak")}:</span>
<span className="download-group__stat-value"> <span className="download-group__stat-value">
{peakSpeed > 0 ? formatSpeed(peakSpeed) : "0 B/s"} {peakSpeed > 0 ? formatSpeed(peakSpeed) : "0 B/s"}
</span> </span>
</div> </div>
</div> </div>
{game.download?.downloader === Downloader.Torrent &&
isGameDownloading &&
lastPacket &&
(lastPacket.numSeeds > 0 || lastPacket.numPeers > 0) && (
<div className="download-group__stat-item">
<div className="download-group__stat-content">
<span className="download-group__stat-label">
Seeds:{" "}
<span className="download-group__stat-value">
{lastPacket.numSeeds}
</span>
, Peers:{" "}
<span className="download-group__stat-value">
{lastPacket.numPeers}
</span>
</span>
</div>
</div>
)}
{game.download?.downloader && ( {game.download?.downloader && (
<div className="download-group__stat-item"> <div className="download-group__stat-item">
<div <div className="download-group__stat-content">
className="download-group__stat-content"
style={{
justifyContent: "space-between",
alignItems: "center",
}}
>
<Badge>{DOWNLOADER_NAME[game.download.downloader]}</Badge> <Badge>{DOWNLOADER_NAME[game.download.downloader]}</Badge>
{game.download?.downloader === Downloader.Torrent &&
isGameDownloading &&
lastPacket &&
(lastPacket.numSeeds > 0 || lastPacket.numPeers > 0) && (
<span className="download-group__stat-label">
{t("seeds")}{" "}
<span className="download-group__stat-value">
{lastPacket.numSeeds}
</span>
, {t("peers")}{" "}
<span className="download-group__stat-value">
{lastPacket.numPeers}
</span>
</span>
)}
</div> </div>
</div> </div>
)} )}
@@ -873,14 +870,8 @@ export function DownloadGroup({
<li key={game.id} className="download-group__simple-card"> <li key={game.id} className="download-group__simple-card">
<button <button
type="button" type="button"
className="download-group__simple-thumbnail"
onClick={() => navigate(buildGameDetailsPath(game))} onClick={() => navigate(buildGameDetailsPath(game))}
style={{ className="download-group__simple-thumbnail"
background: "none",
border: "none",
padding: 0,
cursor: "pointer",
}}
> >
<img src={game.libraryImageUrl || ""} alt={game.title} /> <img src={game.libraryImageUrl || ""} alt={game.title} />
</button> </button>
@@ -888,18 +879,10 @@ export function DownloadGroup({
<div className="download-group__simple-info"> <div className="download-group__simple-info">
<button <button
type="button" type="button"
className="download-group__simple-title"
onClick={() => navigate(buildGameDetailsPath(game))} onClick={() => navigate(buildGameDetailsPath(game))}
style={{ className="download-group__simple-title-button"
background: "none",
border: "none",
padding: 0,
cursor: "pointer",
textAlign: "left",
width: "100%",
}}
> >
{game.title} <h3 className="download-group__simple-title">{game.title}</h3>
</button> </button>
<div className="download-group__simple-meta"> <div className="download-group__simple-meta">
<div className="download-group__simple-meta-row"> <div className="download-group__simple-meta-row">

View File

@@ -87,16 +87,12 @@ export function LibraryTab({
<ul className="profile-content__games-grid"> <ul className="profile-content__games-grid">
{pinnedGames?.map((game) => ( {pinnedGames?.map((game) => (
<li <li key={game.objectId} style={{ listStyle: "none" }}>
key={game.objectId}
style={{ listStyle: "none" }}
className="user-library-game__wrapper"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
<UserLibraryGameCard <UserLibraryGameCard
game={game} game={game}
statIndex={statsIndex} statIndex={statsIndex}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
sortBy={sortBy} sortBy={sortBy}
/> />
</li> </li>
@@ -138,9 +134,6 @@ export function LibraryTab({
<motion.li <motion.li
key={`${sortBy}-${game.objectId}`} key={`${sortBy}-${game.objectId}`}
style={{ listStyle: "none" }} style={{ listStyle: "none" }}
className="user-library-game__wrapper"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
initial={ initial={
isNewGame isNewGame
? { opacity: 0.5, y: 15, scale: 0.96 } ? { opacity: 0.5, y: 15, scale: 0.96 }
@@ -167,6 +160,8 @@ export function LibraryTab({
<UserLibraryGameCard <UserLibraryGameCard
game={game} game={game}
statIndex={statsIndex} statIndex={statsIndex}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
sortBy={sortBy} sortBy={sortBy}
/> />
</motion.li> </motion.li>

View File

@@ -25,12 +25,16 @@ import "./user-library-game-card.scss";
interface UserLibraryGameCardProps { interface UserLibraryGameCardProps {
game: UserGame; game: UserGame;
statIndex: number; statIndex: number;
onMouseEnter: () => void;
onMouseLeave: () => void;
sortBy?: string; sortBy?: string;
} }
export function UserLibraryGameCard({ export function UserLibraryGameCard({
game, game,
statIndex, statIndex,
onMouseEnter,
onMouseLeave,
sortBy, sortBy,
}: UserLibraryGameCardProps) { }: UserLibraryGameCardProps) {
const { userProfile, isMe, getUserLibraryGames } = const { userProfile, isMe, getUserLibraryGames } =
@@ -126,119 +130,129 @@ export function UserLibraryGameCard({
return ( return (
<> <>
<button <li
type="button" onMouseEnter={onMouseEnter}
className="user-library-game__cover" onMouseLeave={onMouseLeave}
onClick={() => navigate(buildUserGameDetailsPath(game))} className="user-library-game__wrapper"
title={isTooltipHovered ? undefined : game.title} title={isTooltipHovered ? undefined : game.title}
> >
<div className="user-library-game__overlay"> <button
{isMe && ( type="button"
<div className="user-library-game__actions-container"> className="user-library-game__cover"
<button onClick={() => navigate(buildUserGameDetailsPath(game))}
type="button" >
className="user-library-game__pin-button" <div className="user-library-game__overlay">
onClick={(e) => { {isMe && (
e.stopPropagation(); <div className="user-library-game__actions-container">
toggleGamePinned(); <button
}} type="button"
disabled={isPinning} className="user-library-game__pin-button"
> onClick={(e) => {
{game.isPinned ? ( e.stopPropagation();
<PinSlashIcon size={12} /> toggleGamePinned();
) : ( }}
<PinIcon size={12} /> disabled={isPinning}
)} >
</button> {game.isPinned ? (
</div> <PinSlashIcon size={12} />
)} ) : (
<div <PinIcon size={12} />
className="user-library-game__playtime" )}
data-tooltip-place="top" </button>
data-tooltip-content={ </div>
game.hasManuallyUpdatedPlaytime
? t("manual_playtime_tooltip")
: undefined
}
data-tooltip-id={game.objectId}
>
{game.hasManuallyUpdatedPlaytime ? (
<AlertFillIcon
size={11}
className="user-library-game__manual-playtime"
/>
) : (
<ClockIcon size={11} />
)} )}
<span className="user-library-game__playtime-long"> <div
{formatPlayTime(game.playTimeInSeconds)} className="user-library-game__playtime"
</span> data-tooltip-place="top"
<span className="user-library-game__playtime-short"> data-tooltip-content={
{formatPlayTime(game.playTimeInSeconds, true)} game.hasManuallyUpdatedPlaytime
</span> ? t("manual_playtime_tooltip")
</div> : undefined
}
data-tooltip-id={game.objectId}
>
{game.hasManuallyUpdatedPlaytime ? (
<AlertFillIcon
size={11}
className="user-library-game__manual-playtime"
/>
) : (
<ClockIcon size={11} />
)}
<span className="user-library-game__playtime-long">
{formatPlayTime(game.playTimeInSeconds)}
</span>
<span className="user-library-game__playtime-short">
{formatPlayTime(game.playTimeInSeconds, true)}
</span>
</div>
{userProfile?.hasActiveSubscription &&
game.achievementCount > 0 && (
<div className="user-library-game__stats">
<div className="user-library-game__stats-header">
<div className="user-library-game__stats-content">
<div
className="user-library-game__stats-item"
style={{
transform: `translateY(${-100 * (statIndex % getStatsItemCount())}%)`,
}}
>
<TrophyIcon size={13} />
<span>
{game.unlockedAchievementCount} /{" "}
{game.achievementCount}
</span>
</div>
{game.achievementsPointsEarnedSum > 0 && (
<div
className="user-library-game__stats-item"
style={{
transform: `translateY(${-100 * (statIndex % getStatsItemCount())}%)`,
}}
>
<HydraIcon width={16} height={16} />
{formatAchievementPoints(
game.achievementsPointsEarnedSum
)}
</div>
)}
</div>
{userProfile?.hasActiveSubscription && game.achievementCount > 0 && (
<div className="user-library-game__stats">
<div className="user-library-game__stats-header">
<div className="user-library-game__stats-content">
<div
className="user-library-game__stats-item"
style={{
transform: `translateY(${-100 * (statIndex % getStatsItemCount())}%)`,
}}
>
<TrophyIcon size={13} />
<span> <span>
{game.unlockedAchievementCount} / {game.achievementCount} {formatDownloadProgress(
game.unlockedAchievementCount / game.achievementCount,
1
)}
</span> </span>
</div> </div>
{game.achievementsPointsEarnedSum > 0 && ( <progress
<div max={1}
className="user-library-game__stats-item" value={
style={{ game.unlockedAchievementCount / game.achievementCount
transform: `translateY(${-100 * (statIndex % getStatsItemCount())}%)`, }
}} className="user-library-game__achievements-progress"
> />
<HydraIcon width={16} height={16} />
{formatAchievementPoints(
game.achievementsPointsEarnedSum
)}
</div>
)}
</div> </div>
)}
<span>
{formatDownloadProgress(
game.unlockedAchievementCount / game.achievementCount,
1
)}
</span>
</div>
<progress
max={1}
value={game.unlockedAchievementCount / game.achievementCount}
className="user-library-game__achievements-progress"
/>
</div>
)}
</div>
{imageError || !game.coverImageUrl ? (
<div className="user-library-game__cover-placeholder">
<ImageIcon size={48} />
</div> </div>
) : (
<img {imageError || !game.coverImageUrl ? (
src={game.coverImageUrl} <div className="user-library-game__cover-placeholder">
alt={game.title} <ImageIcon size={48} />
className="user-library-game__game-image" </div>
onError={() => setImageError(true)} ) : (
/> <img
)} src={game.coverImageUrl}
</button> alt={game.title}
className="user-library-game__game-image"
onError={() => setImageError(true)}
/>
)}
</button>
</li>
<Tooltip <Tooltip
id={game.objectId} id={game.objectId}
style={{ style={{

View File

@@ -20,6 +20,8 @@ export interface Auth {
accessToken: string; accessToken: string;
refreshToken: string; refreshToken: string;
tokenExpirationTimestamp: number; tokenExpirationTimestamp: number;
featurebaseJwt: string;
workwondersJwt: string;
} }
export interface User { export interface User {

View File

@@ -6330,7 +6330,7 @@ jsonwebtoken@^9.0.2:
object.assign "^4.1.4" object.assign "^4.1.4"
object.values "^1.1.6" object.values "^1.1.6"
jwa@^1.4.1: jwa@^1.4.2:
version "1.4.2" version "1.4.2"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.2.tgz#16011ac6db48de7b102777e57897901520eec7b9" resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.2.tgz#16011ac6db48de7b102777e57897901520eec7b9"
integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw== integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==
@@ -6340,11 +6340,11 @@ jwa@^1.4.1:
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
jws@^3.2.2: jws@^3.2.2:
version "3.2.2" version "3.2.3"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.3.tgz#5ac0690b460900a27265de24520526853c0b8ca1"
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== integrity sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==
dependencies: dependencies:
jwa "^1.4.1" jwa "^1.4.2"
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
keyv@^4.0.0, keyv@^4.5.3: keyv@^4.0.0, keyv@^4.5.3: