mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
Revert "More friendly experience when presenting repack options"
This commit is contained in:
parent
e5fec91062
commit
2cb76a9ad4
32 changed files with 16 additions and 794 deletions
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -96,8 +96,6 @@
|
|||
"copy_to_clipboard": "Копировать",
|
||||
"copied_to_clipboard": "Скопировано",
|
||||
"got_it": "Понятно",
|
||||
"multi_language": "Мультиязычный",
|
||||
"multiplayer": "Многопользовательский",
|
||||
"no_shop_details": "Не удалось получить описание",
|
||||
"download_options": "Вариантов загрузки",
|
||||
"download_path": "Путь для загрузок",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
|
@ -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";
|
||||
|
|
|
@ -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
107
src/preload/index.d.ts
vendored
|
@ -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),
|
||||
});
|
|
@ -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),
|
||||
});
|
||||
|
|
|
@ -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 |
|
@ -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 |
|
@ -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";
|
||||
|
|
|
@ -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",
|
||||
});
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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",
|
||||
});
|
|
@ -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>
|
||||
);
|
||||
}
|
3
src/renderer/src/declaration.d.ts
vendored
3
src/renderer/src/declaration.d.ts
vendored
|
@ -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 {
|
||||
|
|
|
@ -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";
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
export function toCapitalize(str: string) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
|
@ -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",
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
export type TorrentData = {
|
||||
seeders: number;
|
||||
peers: number;
|
||||
lastTracked?: Date;
|
||||
};
|
|
@ -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,
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue