Revert "More friendly experience when presenting repack options"

This commit is contained in:
Zamitto 2024-05-18 16:26:16 -03:00 committed by GitHub
parent e5fec91062
commit 2cb76a9ad4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 16 additions and 794 deletions

View file

@ -96,8 +96,6 @@
"copy_to_clipboard": "Copy",
"copied_to_clipboard": "Copied",
"got_it": "Got it",
"multi_language": "Multi Language",
"multiplayer": "Multi Player",
"no_shop_details": "Could not retrieve shop details.",
"download_options": "Download options",
"download_path": "Download path",

View file

@ -95,8 +95,6 @@
"dont_show_it_again": "No mostrar de nuevo",
"copy_to_clipboard": "Copiar",
"copied_to_clipboard": "Copiado",
"multi_language": "Multi Idioma",
"multiplayer": "Multijugador",
"got_it": "Entendido",
"no_shop_details": "No se pudieron obtener detalles de la tienda.",
"download_options": "Opciones de descarga",

View file

@ -75,9 +75,7 @@
"close": "Fermer",
"deleting": "Suppression du programme d'installation…",
"playing_now": "Jeu en cours",
"last_time_played": "Dernièrement joué {{période}}",
"multi_language": "Multilingue",
"multiplayer": "Multijoueur"
"last_time_played": "Dernièrement joué {{période}}"
},
"activation": {
"title": "Activer Hydra",

View file

@ -84,9 +84,7 @@
"repacks_modal_description": "Choose the repack you want to download",
"downloads_path": "Letöltések helye",
"select_folder_hint": "Ahhoz, hogy megváltoztasd a helyet, hozzákell férned a",
"download_now": "Töltsd le most",
"multi_language": "Többnyelvű",
"multiplayer": "Többjátékos"
"download_now": "Töltsd le most"
},
"activation": {
"title": "Hydra Aktiválása",

View file

@ -96,9 +96,7 @@
"dont_show_it_again": "Non mostrarlo più",
"copy_to_clipboard": "Copia",
"copied_to_clipboard": "Copiato",
"got_it": "Capito",
"multi_language": "Multilingua",
"multiplayer": "Multigiocatore"
"got_it": "Capito"
},
"activation": {
"title": "Attiva Hydra",

View file

@ -87,9 +87,6 @@
"change": "Zmień",
"repacks_modal_description": "Wybierz repack, który chcesz pobrać",
"select_folder_hint": "Aby zmienić domyślny folder, przejdź do",
"settings": "Ustawienia Hydra",
"multi_language": "Wielojęzyczny",
"multiplayer": "Wieloosobowy",
"download_now": "Pobierz teraz",
"installation_instructions": "Instrukcja instalacji",
"installation_instructions_description": "Do zainstalowania tej gry wymagane są dodatkowe kroki",

View file

@ -92,8 +92,6 @@
"copy_to_clipboard": "Copiar",
"copied_to_clipboard": "Copiado",
"got_it": "Entendi",
"multi_language": "Multi Idioma",
"multiplayer": "Multijogador",
"no_shop_details": "Não foi possível obter os detalhes da loja.",
"download_options": "Opções de download",
"download_path": "Diretório de download",

View file

@ -96,8 +96,6 @@
"copy_to_clipboard": "Копировать",
"copied_to_clipboard": "Скопировано",
"got_it": "Понятно",
"multi_language": "Мультиязычный",
"multiplayer": "Многопользовательский",
"no_shop_details": "Не удалось получить описание",
"download_options": "Вариантов загрузки",
"download_path": "Путь для загрузок",

View file

@ -96,9 +96,7 @@
"dont_show_it_again": "Tekrar gösterme",
"copy_to_clipboard": "Kopyala",
"copied_to_clipboard": "Kopyalandı",
"got_it": "Tamam",
"multi_language": "Çoklu Dil",
"multiplayer": "Çok Oyunculu"
"got_it": "Tamam"
},
"activation": {
"title": "Hydra'yı aktif et",

View file

@ -1,11 +0,0 @@
import { webTorrentData } from "@main/services/web-torrent-data";
import { registerEvent } from "../../register-event";
const getMagnetHealth = async (
_event: Electron.IpcMainInvokeEvent,
magnet: string
) => {
return webTorrentData.getSeedersAndPeers(magnet);
};
registerEvent("getMagnetHealth", getMagnetHealth);

View file

@ -7,7 +7,6 @@ import "./catalogue/get-games";
import "./catalogue/get-how-long-to-beat";
import "./catalogue/get-random-game";
import "./catalogue/search-games";
import "./catalogue/repacks/get-magnet-health";
import "./catalogue/search-game-repacks";
import "./hardware/get-disk-free-space";
import "./library/add-game-to-library";

View file

@ -1,40 +0,0 @@
import webTorrentHealth from "webtorrent-health";
type WebTorrentHealthData = {
seeds: number;
peers: number;
};
const MILLISECONDS = 1000;
const SECONDS = 1.5;
export const webTorrentData = {
async getSeedersAndPeers(
magnet: string
): Promise<{ seeders: number; peers: number } | null> {
let peers = 0;
let seeds = 0;
let retry = 0;
let timeout = SECONDS * MILLISECONDS;
while (retry < 3) {
try {
const data: WebTorrentHealthData = await webTorrentHealth(magnet, {
timeout,
});
peers = data.peers;
seeds = data.seeds;
if (peers || seeds) break;
} catch (e) {
return null;
}
timeout *= 2;
retry++;
}
return { peers, seeders: seeds };
},
};

107
src/preload/index.d.ts vendored
View file

@ -1,107 +0,0 @@
// See the Electron documentation for details on how to use preload scripts:
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
import { contextBridge, ipcRenderer } from "electron";
import type {
CatalogueCategory,
GameShop,
TorrentProgress,
UserPreferences,
} from "@types";
contextBridge.exposeInMainWorld("electron", {
/* Torrenting */
startGameDownload: (
repackId: number,
objectID: string,
title: string,
shop: GameShop
) => ipcRenderer.invoke("startGameDownload", repackId, objectID, title, shop),
cancelGameDownload: (gameId: number) =>
ipcRenderer.invoke("cancelGameDownload", gameId),
pauseGameDownload: (gameId: number) =>
ipcRenderer.invoke("pauseGameDownload", gameId),
resumeGameDownload: (gameId: number) =>
ipcRenderer.invoke("resumeGameDownload", gameId),
onDownloadProgress: (cb: (value: TorrentProgress) => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
value: TorrentProgress
) => cb(value);
ipcRenderer.on("on-download-progress", listener);
return () => ipcRenderer.removeListener("on-download-progress", listener);
},
/* Catalogue */
searchGames: (query: string) => ipcRenderer.invoke("searchGames", query),
getCatalogue: (category: CatalogueCategory) =>
ipcRenderer.invoke("getCatalogue", category),
getGameShopDetails: (objectID: string, shop: GameShop, language: string) =>
ipcRenderer.invoke("getGameShopDetails", objectID, shop, language),
getRandomGame: () => ipcRenderer.invoke("getRandomGame"),
getHowLongToBeat: (objectID: string, shop: GameShop, title: string) =>
ipcRenderer.invoke("getHowLongToBeat", objectID, shop, title),
getGames: (take?: number, prevCursor?: number) =>
ipcRenderer.invoke("getGames", take, prevCursor),
/* User preferences */
getUserPreferences: () => ipcRenderer.invoke("getUserPreferences"),
updateUserPreferences: (preferences: UserPreferences) =>
ipcRenderer.invoke("updateUserPreferences", preferences),
autoLaunch: (enabled: boolean) => ipcRenderer.invoke("autoLaunch", enabled),
/* Library */
addGameToLibrary: (
objectID: string,
title: string,
shop: GameShop,
executablePath: string
) =>
ipcRenderer.invoke(
"addGameToLibrary",
objectID,
title,
shop,
executablePath
),
getLibrary: () => ipcRenderer.invoke("getLibrary"),
getRepackersFriendlyNames: () =>
ipcRenderer.invoke("getRepackersFriendlyNames"),
openGameInstaller: (gameId: number) =>
ipcRenderer.invoke("openGameInstaller", gameId),
openGame: (gameId: number, executablePath: string) =>
ipcRenderer.invoke("openGame", gameId, executablePath),
closeGame: (gameId: number) => ipcRenderer.invoke("closeGame", gameId),
removeGameFromLibrary: (gameId: number) =>
ipcRenderer.invoke("removeGameFromLibrary", gameId),
deleteGameFolder: (gameId: number) =>
ipcRenderer.invoke("deleteGameFolder", gameId),
getGameByObjectID: (objectID: string) =>
ipcRenderer.invoke("getGameByObjectID", objectID),
onPlaytime: (cb: (gameId: number) => void) => {
const listener = (_event: Electron.IpcRendererEvent, gameId: number) =>
cb(gameId);
ipcRenderer.on("on-playtime", listener);
return () => ipcRenderer.removeListener("on-playtime", listener);
},
onGameClose: (cb: (gameId: number) => void) => {
const listener = (_event: Electron.IpcRendererEvent, gameId: number) =>
cb(gameId);
ipcRenderer.on("on-game-close", listener);
return () => ipcRenderer.removeListener("on-game-close", listener);
},
/* Hardware */
getDiskFreeSpace: () => ipcRenderer.invoke("getDiskFreeSpace"),
/* Misc */
ping: () => ipcRenderer.invoke("ping"),
getVersion: () => ipcRenderer.invoke("getVersion"),
getDefaultDownloadsPath: () => ipcRenderer.invoke("getDefaultDownloadsPath"),
openExternal: (src: string) => ipcRenderer.invoke("openExternal", src),
showOpenDialog: (options: Electron.OpenDialogOptions) =>
ipcRenderer.invoke("showOpenDialog", options),
platform: process.platform,
getMagnetHealth: (magnet: string) =>
ipcRenderer.invoke("getMagnetHealth", magnet),
});

View file

@ -112,6 +112,4 @@ contextBridge.exposeInMainWorld("electron", {
showOpenDialog: (options: Electron.OpenDialogOptions) =>
ipcRenderer.invoke("showOpenDialog", options),
platform: process.platform,
getMagnetHealth: (magnet: string) =>
ipcRenderer.invoke("getMagnetHealth", magnet),
});

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sprout"><path d="M7 20h10"/><path d="M10 20c5.5-2.5.8-6.4 3-10"/><path d="M9.5 9.4c1.1.8 1.8 2.2 2.3 3.7-2 .4-3.5.4-4.8-.3-1.2-.6-2.3-1.9-3-4.2 2.8-.5 4.4 0 5.5.8z"/><path d="M14.1 6a7 7 0 0 0-1.1 4c1.9-.1 3.3-.6 4.3-1.4 1-1 1.6-2.3 1.7-4.6-2.7.1-4 1-4.9 2z"/></svg>

Before

Width:  |  Height:  |  Size: 469 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-users"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>

Before

Width:  |  Height:  |  Size: 373 B

View file

@ -8,4 +8,3 @@ export * from "./sidebar/sidebar";
export * from "./text-field/text-field";
export * from "./checkbox-field/checkbox-field";
export * from "./link/link";
export * from "./tag/tag";

View file

@ -1,12 +0,0 @@
import { SPACING_UNIT, vars } from "../../theme.css";
import { style } from "@vanilla-extract/css";
export const tagStyle = style({
borderRadius: "3px",
border: `1px solid ${vars.color.border}`,
padding: `${SPACING_UNIT / 4}px ${SPACING_UNIT}px`,
});
export const tagText = style({
fontSize: "11px",
});

View file

@ -1,9 +0,0 @@
import * as styles from "./tag.css";
export function Tag({ children }: Readonly<{ children: React.ReactNode }>) {
return (
<div className={styles.tagStyle}>
<span className={styles.tagText}>{children}</span>
</div>
);
}

View file

@ -1,36 +0,0 @@
import { style } from "@vanilla-extract/css";
export const tooltipStyle = style({
position: "relative",
display: "flex",
cursor: "pointer",
alignItems: "center",
});
export const tooltipTextStyle = style({
visibility: "hidden",
backgroundColor: "#555",
color: "#fff",
textAlign: "center",
borderRadius: "6px",
padding: "5px 5px",
position: "absolute",
zIndex: "1",
bottom: "125%",
left: "max(0%, min(100%, 50%))",
transform: "translateX(-50%)",
":after": {
content: '""',
position: "absolute",
top: "100%",
left: "50%",
marginLeft: "-5px",
borderWidth: "5px",
borderStyle: "solid",
borderColor: "#555 transparent transparent transparent",
},
});
export const tooltipVisible = style({
visibility: "visible",
});

View file

@ -1,26 +0,0 @@
import { useState } from "react";
import * as styles from "./tooltip.css";
interface TooltipProps {
children: React.ReactNode;
tooltipText: string;
}
export function Tooltip({ children, tooltipText }: Readonly<TooltipProps>) {
const [isVisible, setIsVisible] = useState(false);
return (
<div
className={styles.tooltipStyle}
onMouseEnter={() => setIsVisible(true)}
onMouseLeave={() => setIsVisible(false)}
>
{children}
<span
className={`${styles.tooltipTextStyle} ${isVisible ? styles.tooltipVisible : ""}`}
>
{tooltipText}
</span>
</div>
);
}

View file

@ -90,9 +90,6 @@ declare global {
options: Electron.OpenDialogOptions
) => Promise<Electron.OpenDialogReturnValue>;
platform: NodeJS.Platform;
getMagnetHealth: (
magnet: string
) => Promise<{ seeders: number; peers: number }>;
}
interface Window {

View file

@ -1,39 +0,0 @@
import { toCapitalize } from "./string";
export const isMultiplayerRepack = (title: string, repacker: string) => {
const titleToLower = title.toLowerCase();
const repackerToLower = repacker.toLowerCase();
return (
titleToLower.includes("multiplayer") ||
titleToLower.includes("onlinefix") ||
titleToLower.includes("online fix") ||
repackerToLower.includes("onlinefix") ||
repackerToLower.includes("online fix")
);
};
export const supportMultiLanguage = (title: string) => {
const multiFollowedByDigitsRegex = /multi\d+/;
return multiFollowedByDigitsRegex.test(title.toLowerCase());
};
export const getRepackLanguageBasedOnRepacker = (
repacker: string,
userLanguage: string
) => {
const languageCodes = {
xatab: "ru",
};
const languageCode = languageCodes[repacker.toLowerCase()] || "en";
const displayNames = new Intl.DisplayNames([userLanguage], {
type: "language",
});
const language = displayNames.of(languageCode);
return language ? toCapitalize(language) : "English";
};

View file

@ -1,3 +0,0 @@
export function toCapitalize(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1);
}

View file

@ -16,9 +16,3 @@ export const repackButton = style({
color: vars.color.bodyText,
padding: `${SPACING_UNIT * 2}px`,
});
export const tagsContainer = style({
display: "flex",
gap: `${SPACING_UNIT}px`,
flexWrap: "wrap",
});

View file

@ -9,14 +9,6 @@ import * as styles from "./repacks-modal.css";
import { SPACING_UNIT } from "../../theme.css";
import { format } from "date-fns";
import { SelectFolderModal } from "./select-folder-modal";
import { SeedersAndPeers } from "./seeders-and-peers/seeders-and-peers";
import {
isMultiplayerRepack,
supportMultiLanguage,
} from "@renderer/helpers/searcher";
import { Tag } from "@renderer/components/tag/tag";
import { getRepackLanguageBasedOnRepacker } from "../../helpers/searcher";
import { useAppSelector } from "@renderer/hooks";
export interface RepacksModalProps {
visible: boolean;
@ -34,9 +26,6 @@ export function RepacksModal({
const [filteredRepacks, setFilteredRepacks] = useState<GameRepack[]>([]);
const [repack, setRepack] = useState<GameRepack | null>(null);
const [showSelectFolderModal, setShowSelectFolderModal] = useState(false);
const { value: userPreferences } = useAppSelector(
(state) => state.userPreferences
);
const { t } = useTranslation("game_details");
@ -94,41 +83,12 @@ export function RepacksModal({
<p style={{ color: "#DADBE1", wordBreak: "break-word" }}>
{repack.title}
</p>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: "100%",
}}
>
<div>
<p style={{ fontSize: "12px" }}>
{repack.fileSize} - {repack.repacker} -{" "}
{repack.uploadDate
? format(repack.uploadDate, "dd/MM/yyyy")
: ""}
{userPreferences?.language && (
<>
{" - " +
getRepackLanguageBasedOnRepacker(
repack.repacker,
userPreferences?.language
)}
</>
)}
</p>
</div>
<SeedersAndPeers repack={repack} />
</div>
<div className={styles.tagsContainer}>
{supportMultiLanguage(repack.title) && (
<Tag>{t("multi_language")}</Tag>
)}
{isMultiplayerRepack(repack.title, repack.repacker) && (
<Tag>{t("multiplayer")}</Tag>
)}
</div>
<p style={{ fontSize: "12px" }}>
{repack.fileSize} - {repack.repacker} -{" "}
{repack.uploadDate
? format(repack.uploadDate, "dd/MM/yyyy")
: ""}
</p>
</Button>
))}
</div>

View file

@ -1,20 +0,0 @@
import Skeleton from "react-loading-skeleton";
export function SeedersAndPeersSkeleton() {
return (
<div
style={{
display: "flex",
}}
>
<Skeleton width={40} height={20} />
<Skeleton
width={40}
height={20}
style={{
marginLeft: "12px",
}}
/>
</div>
);
}

View file

@ -1,55 +0,0 @@
import { GameRepack } from "@types";
import UsersIcon from "@renderer/assets/users-icon.svg?react";
import SproutIcon from "@renderer/assets/sprout-icon.svg?react";
import { useMagnetHealth } from "./useMagnetHealth";
import { Tooltip } from "@renderer/components/tooltip/tooltip";
import { SeedersAndPeersSkeleton } from "./seeders-and-peers-skeleton";
import { SPACING_UNIT, vars } from "@renderer/theme.css";
interface SeedersAndPeersProps {
repack: GameRepack;
}
export function SeedersAndPeers({ repack }: Readonly<SeedersAndPeersProps>) {
const { magnetData, isLoading, error } = useMagnetHealth(repack.magnet);
if (isLoading) {
return <SeedersAndPeersSkeleton />;
}
if (error) {
return null;
}
return (
<div
style={{
display: "flex",
justifyContent: "space-around",
}}
>
<Tooltip tooltipText="Seeders">
<SproutIcon stroke={vars.color.bodyText} />
<span
style={{
marginLeft: `${SPACING_UNIT - 5}px`,
marginRight: `${SPACING_UNIT}px`,
}}
>
{magnetData?.seeders}
</span>
</Tooltip>
<Tooltip tooltipText="Peers">
<UsersIcon stroke={vars.color.bodyText} />
<span
style={{
marginLeft: `${SPACING_UNIT - 5}px`,
}}
>
{magnetData?.peers}
</span>
</Tooltip>
</div>
);
}

View file

@ -1,5 +0,0 @@
export type TorrentData = {
seeders: number;
peers: number;
lastTracked?: Date;
};

View file

@ -1,80 +0,0 @@
import { useEffect, useState } from "react";
import { TorrentData } from "./types";
const cache: Record<string, TorrentData> = {};
export function useMagnetHealth(magnet: string) {
const [magnetData, setMagnetData] = useState<TorrentData | null>(
cache[magnet] || null
);
const [isLoading, setIsLoading] = useState(() => {
if (cache[magnet]) {
return false;
}
return true;
});
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
if (!magnet) {
return;
}
if (cache[magnet]) {
setMagnetData(cache[magnet]);
setIsLoading(false);
return;
}
window.electron
.getMagnetHealth(magnet)
.then(
(result) => {
if (result) {
setMagnetData(result);
setIsLoading(false);
cache[magnet] = result;
cache[magnet].lastTracked = new Date();
}
},
(error) => {
setError(error);
setIsLoading(false);
}
)
.catch(() => {});
}, [magnet]);
useEffect(() => {
function invalidateCache() {
const TWO_MINUTES = 2 * 60 * 1000;
const cacheExpiresIn = TWO_MINUTES;
Object.keys(cache).forEach((key) => {
const lastTracked = cache[key].lastTracked;
if (!lastTracked) {
return;
}
if (Date.now() - lastTracked.getTime() > cacheExpiresIn) {
delete cache[key];
}
});
}
invalidateCache();
return () => {
invalidateCache();
};
}, []);
return {
magnetData,
isLoading,
error,
};
}