mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-03 08:43:48 +03:00
feat: adding accuracy as text instead of colours
This commit is contained in:
parent
05a31cf6a6
commit
c0274ee7e7
@ -2,5 +2,4 @@ module.exports = {
|
|||||||
semi: true,
|
semi: true,
|
||||||
trailingComma: "all",
|
trailingComma: "all",
|
||||||
singleQuote: false,
|
singleQuote: false,
|
||||||
tabWidth: 2,
|
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
# Hydra
|
# Hydra
|
||||||
|
|
||||||
<a href="https://discord.gg/hydralauncher" target="_blank">![Discord](https://img.shields.io/discord/1220692017311645737?style=flat&logo=discord&label=Hydra&labelColor=%231c1c1c)</a>
|
<a href="https://discord.gg/hydralauncher" target="_blank">![Discord](https://img.shields.io/discord/1220692017311645737?style=flat&logo=discord&label=Hydra&labelColor=%231c1c1c)</a>
|
||||||
|
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/hydralauncher/hydra/build.yml)
|
||||||
|
![GitHub package.json version](https://img.shields.io/github/package-json/v/hydralauncher/hydra)
|
||||||
|
|
||||||
Hydra is a game launcher with its own embedded bittorrent client and a self-managed repack scraper.
|
Hydra is a game launcher with its own embedded bittorrent client and a self-managed repack scraper.
|
||||||
The launcher is written in TypeScript (Electron) and Python, which handles the torrenting system by using [libtorrent](https://www.libtorrent.org/).
|
The launcher is written in TypeScript (Electron) and Python, which handles the torrenting system by using [libtorrent](https://www.libtorrent.org/).
|
||||||
|
@ -58,12 +58,9 @@
|
|||||||
"publisher": "Published by {{publisher}}",
|
"publisher": "Published by {{publisher}}",
|
||||||
"copy_link_to_clipboard": "Copy link",
|
"copy_link_to_clipboard": "Copy link",
|
||||||
"copied_link_to_clipboard": "Link copied",
|
"copied_link_to_clipboard": "Link copied",
|
||||||
"main_story": "Main story",
|
|
||||||
"main_plus_sides": "Main story + sides",
|
|
||||||
"completionist": "Completionist",
|
|
||||||
"all_styles": "All styles",
|
|
||||||
"hours": "hours",
|
"hours": "hours",
|
||||||
"minutes": "minutes"
|
"minutes": "minutes",
|
||||||
|
"accuracy": "{{accuracy}}% accuracy"
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Activate Hydra",
|
"title": "Activate Hydra",
|
||||||
|
@ -58,12 +58,9 @@
|
|||||||
"publisher": "Publicado por {{publisher}}",
|
"publisher": "Publicado por {{publisher}}",
|
||||||
"copy_link_to_clipboard": "Copiar enlace",
|
"copy_link_to_clipboard": "Copiar enlace",
|
||||||
"copied_link_to_clipboard": "Enlace copiado",
|
"copied_link_to_clipboard": "Enlace copiado",
|
||||||
"main_story": "Historia principal",
|
|
||||||
"main_plus_sides": "Historia principal + extras",
|
|
||||||
"completionist": "Completista",
|
|
||||||
"all_styles": "Todo",
|
|
||||||
"hours": "horas",
|
"hours": "horas",
|
||||||
"minutes": "minutos"
|
"minutes": "minutos",
|
||||||
|
"accuracy": "{{accuracy}}% precisión"
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Activar Hydra",
|
"title": "Activar Hydra",
|
||||||
|
@ -58,12 +58,9 @@
|
|||||||
"publisher": "Publicado por {{publisher}}",
|
"publisher": "Publicado por {{publisher}}",
|
||||||
"copy_link_to_clipboard": "Copiar link",
|
"copy_link_to_clipboard": "Copiar link",
|
||||||
"copied_link_to_clipboard": "Link copiado",
|
"copied_link_to_clipboard": "Link copiado",
|
||||||
"main_story": "História principal",
|
|
||||||
"main_plus_sides": "Historia principal + extras",
|
|
||||||
"completionist": "Completista",
|
|
||||||
"all_styles": "Tudo",
|
|
||||||
"hours": "horas",
|
"hours": "horas",
|
||||||
"minutes": "minutos"
|
"minutes": "minutos",
|
||||||
|
"accuracy": "{{accuracy}}% de precisão"
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Ativação",
|
"title": "Ativação",
|
||||||
|
@ -2,6 +2,7 @@ import { formatName } from "@main/helpers";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { JSDOM } from "jsdom";
|
import { JSDOM } from "jsdom";
|
||||||
import { requestWebPage } from "./repack-tracker/helpers";
|
import { requestWebPage } from "./repack-tracker/helpers";
|
||||||
|
import { HowLongToBeatCategory } from "@types";
|
||||||
|
|
||||||
export interface HowLongToBeatResult {
|
export interface HowLongToBeatResult {
|
||||||
game_id: number;
|
game_id: number;
|
||||||
@ -27,23 +28,15 @@ export const searchHowLongToBeat = async (gameName: string) => {
|
|||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
|
||||||
Referer: "https://howlongtobeat.com/",
|
Referer: "https://howlongtobeat.com/",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.data as HowLongToBeatSearchResponse;
|
return response.data as HowLongToBeatSearchResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const classNameColor = {
|
export const getHowLongToBeatGame = async (
|
||||||
time_40: "#ff3a3a",
|
id: string,
|
||||||
time_50: "#cc3b51",
|
): Promise<HowLongToBeatCategory[]> => {
|
||||||
time_60: "#824985",
|
|
||||||
time_70: "#5650a1",
|
|
||||||
time_80: "#485cab",
|
|
||||||
time_90: "#3a6db5",
|
|
||||||
time_100: "#287fc2",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getHowLongToBeatGame = async (id: string) => {
|
|
||||||
const response = await requestWebPage(`https://howlongtobeat.com/game/${id}`);
|
const response = await requestWebPage(`https://howlongtobeat.com/game/${id}`);
|
||||||
|
|
||||||
const { window } = new JSDOM(response);
|
const { window } = new JSDOM(response);
|
||||||
@ -54,12 +47,14 @@ export const getHowLongToBeatGame = async (id: string) => {
|
|||||||
|
|
||||||
return $lis.map(($li) => {
|
return $lis.map(($li) => {
|
||||||
const title = $li.querySelector("h4").textContent;
|
const title = $li.querySelector("h4").textContent;
|
||||||
const [, time] = Array.from(($li as HTMLElement).classList);
|
const [, accuracyClassName] = Array.from(($li as HTMLElement).classList);
|
||||||
|
|
||||||
|
const accuracy = accuracyClassName.split("time_").at(1);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
duration: $li.querySelector("h5").textContent,
|
duration: $li.querySelector("h5").textContent,
|
||||||
color: classNameColor[time as keyof typeof classNameColor],
|
accuracy,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -139,19 +139,19 @@ export const descriptionHeaderInfo = style({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const howLongToBeatCategoriesList = style({
|
export const howLongToBeatCategoriesList = style({
|
||||||
margin: 0,
|
margin: "0",
|
||||||
padding: 16,
|
padding: "16px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
gap: 16,
|
gap: "16px",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const howLongToBeatCategory = style({
|
export const howLongToBeatCategory = style({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
gap: 4,
|
gap: "4px",
|
||||||
backgroundColor: vars.color.background,
|
backgroundColor: vars.color.background,
|
||||||
borderRadius: 8,
|
borderRadius: "8px",
|
||||||
padding: `8px 16px`,
|
padding: `8px 16px`,
|
||||||
border: `solid 1px ${vars.color.borderColor}`,
|
border: `solid 1px ${vars.color.borderColor}`,
|
||||||
});
|
});
|
||||||
@ -161,13 +161,19 @@ export const howLongToBeatCategoryLabel = style({
|
|||||||
color: "#DADBE1",
|
color: "#DADBE1",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const howLongToBeatCategorySkeleton = style({
|
||||||
|
border: `solid 1px ${vars.color.borderColor}`,
|
||||||
|
borderRadius: "8px",
|
||||||
|
height: "76px",
|
||||||
|
});
|
||||||
|
|
||||||
globalStyle(".bb_tag", {
|
globalStyle(".bb_tag", {
|
||||||
marginTop: `${SPACING_UNIT * 2}px`,
|
marginTop: `${SPACING_UNIT * 2}px`,
|
||||||
marginBottom: `${SPACING_UNIT * 2}px`,
|
marginBottom: `${SPACING_UNIT * 2}px`,
|
||||||
});
|
});
|
||||||
|
|
||||||
globalStyle(`${description} img`, {
|
globalStyle(`${description} img`, {
|
||||||
borderRadius: 5,
|
borderRadius: "5px",
|
||||||
marginTop: `${SPACING_UNIT}px`,
|
marginTop: `${SPACING_UNIT}px`,
|
||||||
marginBottom: `${SPACING_UNIT}px`,
|
marginBottom: `${SPACING_UNIT}px`,
|
||||||
marginLeft: "auto",
|
marginLeft: "auto",
|
||||||
|
@ -31,9 +31,10 @@ export function GameDetails() {
|
|||||||
const [color, setColor] = useState("");
|
const [color, setColor] = useState("");
|
||||||
const [clipboardLock, setClipboardLock] = useState(false);
|
const [clipboardLock, setClipboardLock] = useState(false);
|
||||||
const [gameDetails, setGameDetails] = useState<ShopDetails | null>(null);
|
const [gameDetails, setGameDetails] = useState<ShopDetails | null>(null);
|
||||||
const [howLongToBeat, setHowLongToBeat] = useState<
|
const [howLongToBeat, setHowLongToBeat] = useState<{
|
||||||
HowLongToBeatCategory[] | null
|
isLoading: boolean;
|
||||||
>(null);
|
data: HowLongToBeatCategory[] | null;
|
||||||
|
}>({ isLoading: true, data: null });
|
||||||
|
|
||||||
const [game, setGame] = useState<Game | null>(null);
|
const [game, setGame] = useState<Game | null>(null);
|
||||||
const [activeRequirement, setActiveRequirement] =
|
const [activeRequirement, setActiveRequirement] =
|
||||||
@ -81,7 +82,7 @@ export function GameDetails() {
|
|||||||
window.electron
|
window.electron
|
||||||
.getHowLongToBeat(objectID, "steam", result.name)
|
.getHowLongToBeat(objectID, "steam", result.name)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setHowLongToBeat(data);
|
setHowLongToBeat({ isLoading: false, data });
|
||||||
});
|
});
|
||||||
|
|
||||||
setGameDetails(result);
|
setGameDetails(result);
|
||||||
@ -89,7 +90,7 @@ export function GameDetails() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
getGame();
|
getGame();
|
||||||
setHowLongToBeat(null);
|
setHowLongToBeat({ isLoading: true, data: null });
|
||||||
setClipboardLock(false);
|
setClipboardLock(false);
|
||||||
}, [getGame, dispatch, navigate, objectID, i18n.language]);
|
}, [getGame, dispatch, navigate, objectID, i18n.language]);
|
||||||
|
|
||||||
@ -103,12 +104,12 @@ export function GameDetails() {
|
|||||||
shop,
|
shop,
|
||||||
encodeURIComponent(gameDetails?.name),
|
encodeURIComponent(gameDetails?.name),
|
||||||
i18n.language,
|
i18n.language,
|
||||||
])
|
]),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(
|
||||||
OPEN_HYDRA_URL + `/?${searchParams.toString()}`
|
OPEN_HYDRA_URL + `/?${searchParams.toString()}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const zero = performance.now();
|
const zero = performance.now();
|
||||||
@ -134,7 +135,7 @@ export function GameDetails() {
|
|||||||
repackId,
|
repackId,
|
||||||
gameDetails.objectID,
|
gameDetails.objectID,
|
||||||
gameDetails.name,
|
gameDetails.name,
|
||||||
shop as GameShop
|
shop as GameShop,
|
||||||
).then(() => {
|
).then(() => {
|
||||||
getGame();
|
getGame();
|
||||||
setShowRepacksModal(false);
|
setShowRepacksModal(false);
|
||||||
@ -217,7 +218,10 @@ export function GameDetails() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.contentSidebar}>
|
<div className={styles.contentSidebar}>
|
||||||
<HowLongToBeatSection howLongToBeatData={howLongToBeat} />
|
<HowLongToBeatSection
|
||||||
|
howLongToBeatData={howLongToBeat.data}
|
||||||
|
isLoading={howLongToBeat.isLoading}
|
||||||
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={styles.contentSidebarTitle}
|
className={styles.contentSidebarTitle}
|
||||||
|
@ -1,14 +1,8 @@
|
|||||||
|
import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
|
||||||
import type { HowLongToBeatCategory } from "@types";
|
import type { HowLongToBeatCategory } from "@types";
|
||||||
import * as styles from "./game-details.css";
|
|
||||||
import { vars } from "@renderer/theme.css";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { vars } from "@renderer/theme.css";
|
||||||
const titleTranslation: Record<string, string> = {
|
import * as styles from "./game-details.css";
|
||||||
"Main Story": "main_story",
|
|
||||||
"Main + Sides": "main_plus_sides",
|
|
||||||
Completionist: "completionist",
|
|
||||||
"All Styles": "all_styles",
|
|
||||||
};
|
|
||||||
|
|
||||||
const durationTranslation: Record<string, string> = {
|
const durationTranslation: Record<string, string> = {
|
||||||
Hours: "hours",
|
Hours: "hours",
|
||||||
@ -17,49 +11,59 @@ const durationTranslation: Record<string, string> = {
|
|||||||
|
|
||||||
export interface HowLongToBeatSectionProps {
|
export interface HowLongToBeatSectionProps {
|
||||||
howLongToBeatData: HowLongToBeatCategory[] | null;
|
howLongToBeatData: HowLongToBeatCategory[] | null;
|
||||||
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HowLongToBeatSection({
|
export function HowLongToBeatSection({
|
||||||
howLongToBeatData,
|
howLongToBeatData,
|
||||||
|
isLoading,
|
||||||
}: HowLongToBeatSectionProps) {
|
}: HowLongToBeatSectionProps) {
|
||||||
const { t } = useTranslation("game_details");
|
const { t } = useTranslation("game_details");
|
||||||
|
|
||||||
if (!howLongToBeatData) return null;
|
|
||||||
|
|
||||||
const getDuration = (duration: string) => {
|
const getDuration = (duration: string) => {
|
||||||
const [value, unit] = duration.split(" ");
|
const [value, unit] = duration.split(" ");
|
||||||
return `${value} ${t(durationTranslation[unit])}`;
|
return `${value} ${t(durationTranslation[unit])}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!howLongToBeatData && !isLoading) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<SkeletonTheme baseColor={vars.color.background} highlightColor="#444">
|
||||||
<div className={styles.contentSidebarTitle}>
|
<div className={styles.contentSidebarTitle}>
|
||||||
<h3>HowLongToBeat</h3>
|
<h3>HowLongToBeat</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul className={styles.howLongToBeatCategoriesList}>
|
<ul className={styles.howLongToBeatCategoriesList}>
|
||||||
{howLongToBeatData.map((category) => (
|
{howLongToBeatData
|
||||||
<li
|
? howLongToBeatData.map((category) => (
|
||||||
key={category.title}
|
<li key={category.title} className={styles.howLongToBeatCategory}>
|
||||||
className={styles.howLongToBeatCategory}
|
<p
|
||||||
style={{ backgroundColor: category.color ?? vars.color.background }}
|
className={styles.howLongToBeatCategoryLabel}
|
||||||
>
|
style={{
|
||||||
<p
|
fontWeight: "bold",
|
||||||
className={styles.howLongToBeatCategoryLabel}
|
}}
|
||||||
style={{
|
>
|
||||||
fontWeight: "bold",
|
{category.title}
|
||||||
}}
|
</p>
|
||||||
>
|
|
||||||
{titleTranslation[category.title]
|
<p className={styles.howLongToBeatCategoryLabel}>
|
||||||
? t(titleTranslation[category.title])
|
{getDuration(category.duration)}
|
||||||
: category.title}
|
</p>
|
||||||
</p>
|
|
||||||
<p className={styles.howLongToBeatCategoryLabel}>
|
{category.accuracy !== "00" && (
|
||||||
{getDuration(category.duration)}
|
<small>
|
||||||
</p>
|
{t("accuracy", { accuracy: category.accuracy })}
|
||||||
</li>
|
</small>
|
||||||
))}
|
)}
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
: Array.from({ length: 4 }).map((_, index) => (
|
||||||
|
<Skeleton
|
||||||
|
key={index}
|
||||||
|
className={styles.howLongToBeatCategorySkeleton}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</>
|
</SkeletonTheme>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -56,8 +56,8 @@ export function RepacksModal({
|
|||||||
gameDetails.repacks.filter((repack) =>
|
gameDetails.repacks.filter((repack) =>
|
||||||
repack.title
|
repack.title
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(event.target.value.toLocaleLowerCase())
|
.includes(event.target.value.toLocaleLowerCase()),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -106,5 +106,5 @@ export interface UserPreferences {
|
|||||||
export interface HowLongToBeatCategory {
|
export interface HowLongToBeatCategory {
|
||||||
title: string;
|
title: string;
|
||||||
duration: string;
|
duration: string;
|
||||||
color: string;
|
accuracy: string;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user