mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-02 16:23:48 +03:00
fix: fixing bottom panel scss
This commit is contained in:
parent
f66bdd706b
commit
f35c34fa63
@ -1,23 +1,21 @@
|
|||||||
import type { HowLongToBeatCategory } from "@types";
|
import type { GameShop, HowLongToBeatCategory } from "@types";
|
||||||
import { getHowLongToBeatGame, searchHowLongToBeat } from "@main/services";
|
import { HydraApi } from "@main/services";
|
||||||
|
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { formatName } from "@shared";
|
|
||||||
|
|
||||||
const getHowLongToBeat = async (
|
const getHowLongToBeat = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
title: string
|
shop: GameShop,
|
||||||
|
objectId: string
|
||||||
): Promise<HowLongToBeatCategory[] | null> => {
|
): Promise<HowLongToBeatCategory[] | null> => {
|
||||||
const response = await searchHowLongToBeat(title);
|
const params = new URLSearchParams({
|
||||||
|
shop,
|
||||||
const game = response.data.find((game) => {
|
objectId: objectId.toString(),
|
||||||
return formatName(game.game_name) === formatName(title);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!game) return null;
|
return HydraApi.get(`/games/how-long-to-beat?${params.toString()}`, null, {
|
||||||
const howLongToBeat = await getHowLongToBeatGame(String(game.game_id));
|
needsAuth: false,
|
||||||
|
});
|
||||||
return howLongToBeat;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent("getHowLongToBeat", getHowLongToBeat);
|
registerEvent("getHowLongToBeat", getHowLongToBeat);
|
||||||
|
@ -1,108 +0,0 @@
|
|||||||
import axios from "axios";
|
|
||||||
import { requestWebPage } from "@main/helpers";
|
|
||||||
import type {
|
|
||||||
HowLongToBeatCategory,
|
|
||||||
HowLongToBeatSearchResponse,
|
|
||||||
} from "@types";
|
|
||||||
import { formatName } from "@shared";
|
|
||||||
import { logger } from "./logger";
|
|
||||||
import UserAgent from "user-agents";
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
apiKey: null as string | null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const getHowLongToBeatSearchApiKey = async () => {
|
|
||||||
const userAgent = new UserAgent();
|
|
||||||
|
|
||||||
const document = await requestWebPage("https://howlongtobeat.com/");
|
|
||||||
const scripts = Array.from(document.querySelectorAll("script"));
|
|
||||||
|
|
||||||
const appScript = scripts.find((script) =>
|
|
||||||
script.src.startsWith("/_next/static/chunks/pages/_app")
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!appScript) return null;
|
|
||||||
|
|
||||||
const response = await axios.get(
|
|
||||||
`https://howlongtobeat.com${appScript.src}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"User-Agent": userAgent.toString(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const results = /fetch\("\/api\/search\/"\.concat\("(.*?)"\)/gm.exec(
|
|
||||||
response.data
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!results) return null;
|
|
||||||
|
|
||||||
return results[1];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const searchHowLongToBeat = async (gameName: string) => {
|
|
||||||
state.apiKey = state.apiKey ?? (await getHowLongToBeatSearchApiKey());
|
|
||||||
if (!state.apiKey) return { data: [] };
|
|
||||||
|
|
||||||
const userAgent = new UserAgent();
|
|
||||||
|
|
||||||
const response = await axios
|
|
||||||
.post(
|
|
||||||
`https://howlongtobeat.com/api/search/${state.apiKey}`,
|
|
||||||
{
|
|
||||||
searchType: "games",
|
|
||||||
searchTerms: formatName(gameName).split(" "),
|
|
||||||
searchPage: 1,
|
|
||||||
size: 20,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"User-Agent": userAgent.toString(),
|
|
||||||
Referer: "https://howlongtobeat.com/",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.catch((error) => {
|
|
||||||
logger.error("Error searching HowLongToBeat:", error?.response?.status);
|
|
||||||
return { data: { data: [] } };
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.data as HowLongToBeatSearchResponse;
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseListItems = ($lis: Element[]) => {
|
|
||||||
return $lis.map(($li) => {
|
|
||||||
const title = $li.querySelector("h4")?.textContent;
|
|
||||||
const [, accuracyClassName] = Array.from(($li as HTMLElement).classList);
|
|
||||||
|
|
||||||
const accuracy = accuracyClassName.split("time_").at(1);
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: title ?? "",
|
|
||||||
duration: $li.querySelector("h5")?.textContent ?? "",
|
|
||||||
accuracy: accuracy ?? "",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getHowLongToBeatGame = async (
|
|
||||||
id: string
|
|
||||||
): Promise<HowLongToBeatCategory[]> => {
|
|
||||||
const document = await requestWebPage(`https://howlongtobeat.com/game/${id}`);
|
|
||||||
|
|
||||||
const $ul = document.querySelector(".shadow_shadow ul");
|
|
||||||
if (!$ul) return [];
|
|
||||||
|
|
||||||
const $lis = Array.from($ul.children);
|
|
||||||
|
|
||||||
const [$firstLi] = $lis;
|
|
||||||
|
|
||||||
if ($firstLi.tagName === "DIV") {
|
|
||||||
const $pcData = $lis.find(($li) => $li.textContent?.includes("PC"));
|
|
||||||
return parseListItems(Array.from($pcData?.querySelectorAll("li") ?? []));
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseListItems($lis);
|
|
||||||
};
|
|
@ -4,7 +4,6 @@ export * from "./steam-250";
|
|||||||
export * from "./steam-grid";
|
export * from "./steam-grid";
|
||||||
export * from "./window-manager";
|
export * from "./window-manager";
|
||||||
export * from "./download";
|
export * from "./download";
|
||||||
export * from "./how-long-to-beat";
|
|
||||||
export * from "./process-watcher";
|
export * from "./process-watcher";
|
||||||
export * from "./main-loop";
|
export * from "./main-loop";
|
||||||
export * from "./hydra-api";
|
export * from "./hydra-api";
|
||||||
|
@ -55,8 +55,8 @@ contextBridge.exposeInMainWorld("electron", {
|
|||||||
getGameShopDetails: (objectId: string, shop: GameShop, language: string) =>
|
getGameShopDetails: (objectId: string, shop: GameShop, language: string) =>
|
||||||
ipcRenderer.invoke("getGameShopDetails", objectId, shop, language),
|
ipcRenderer.invoke("getGameShopDetails", objectId, shop, language),
|
||||||
getRandomGame: () => ipcRenderer.invoke("getRandomGame"),
|
getRandomGame: () => ipcRenderer.invoke("getRandomGame"),
|
||||||
getHowLongToBeat: (title: string) =>
|
getHowLongToBeat: (shop: GameShop, objectId: string) =>
|
||||||
ipcRenderer.invoke("getHowLongToBeat", title),
|
ipcRenderer.invoke("getHowLongToBeat", shop, objectId),
|
||||||
getGames: (take?: number, skip?: number) =>
|
getGames: (take?: number, skip?: number) =>
|
||||||
ipcRenderer.invoke("getGames", take, skip),
|
ipcRenderer.invoke("getGames", take, skip),
|
||||||
searchGameRepacks: (query: string) =>
|
searchGameRepacks: (query: string) =>
|
||||||
|
@ -6,8 +6,45 @@
|
|||||||
<title>Hydra</title>
|
<title>Hydra</title>
|
||||||
<meta
|
<meta
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self'; script-src *; style-src 'self' 'unsafe-inline'; img-src 'self' data: local: *; media-src 'self' local: data: *; connect-src *; font-src *;"
|
content="default-src 'self'; script-src * 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://do.featurebase.app/js/sdk.css; img-src 'self' data: local: *; media-src 'self' local: data: *; connect-src *; font-src *;"
|
||||||
/>
|
/>
|
||||||
|
<script>
|
||||||
|
!(function (e, t) {
|
||||||
|
const a = "featurebase-sdk";
|
||||||
|
function n() {
|
||||||
|
if (!t.getElementById(a)) {
|
||||||
|
var e = t.createElement("script");
|
||||||
|
(e.id = a),
|
||||||
|
(e.src = "https://do.featurebase.app/js/sdk.js"),
|
||||||
|
t
|
||||||
|
.getElementsByTagName("script")[0]
|
||||||
|
.parentNode.insertBefore(
|
||||||
|
e,
|
||||||
|
t.getElementsByTagName("script")[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"function" != typeof e.Featurebase &&
|
||||||
|
(e.Featurebase = function () {
|
||||||
|
(e.Featurebase.q = e.Featurebase.q || []).push(arguments);
|
||||||
|
}),
|
||||||
|
"complete" === t.readyState || "interactive" === t.readyState
|
||||||
|
? n()
|
||||||
|
: t.addEventListener("DOMContentLoaded", n);
|
||||||
|
})(window, document);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
Featurebase("initialize_feedback_widget", {
|
||||||
|
organization: "https://hydralauncher.featurebase.app", // Replace this with your organization name, copy-paste the subdomain part from your Featurebase workspace url (e.g. https://*yourorg*.featurebase.app)
|
||||||
|
theme: "light", // required
|
||||||
|
placement: "right", // optional - remove to hide the floating button
|
||||||
|
email: "youruser@example.com", // optional
|
||||||
|
defaultBoard: "yourboardname", // optional - preselect a board
|
||||||
|
locale: "en", // Change the language, view all available languages from https://help.featurebase.app/en/articles/8879098-using-featurebase-in-my-language
|
||||||
|
metadata: null, // Attach session-specific metadata to feedback. Refer to the advanced section for the details: https://help.featurebase.app/en/articles/3774671-advanced#7k8iriyap66
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
@use "../../scss/globals.scss";
|
@use "../../scss/globals.scss";
|
||||||
|
|
||||||
.bottom-panel {
|
.bottom-panel {
|
||||||
width: "100%";
|
width: 100%;
|
||||||
border-top: solid 1px globals.$border-color;
|
border-top: solid 1px globals.$border-color;
|
||||||
background-color: globals.$background-color;
|
background-color: globals.$background-color;
|
||||||
padding: calc(globals.$spacing-unit / 2) calc(globals.$spacing-unit * 2);
|
padding: calc(globals.$spacing-unit / 2) calc(globals.$spacing-unit * 2);
|
||||||
|
3
src/renderer/src/declaration.d.ts
vendored
3
src/renderer/src/declaration.d.ts
vendored
@ -66,7 +66,8 @@ declare global {
|
|||||||
) => Promise<ShopDetails | null>;
|
) => Promise<ShopDetails | null>;
|
||||||
getRandomGame: () => Promise<Steam250Game>;
|
getRandomGame: () => Promise<Steam250Game>;
|
||||||
getHowLongToBeat: (
|
getHowLongToBeat: (
|
||||||
title: string
|
shop: GameShop,
|
||||||
|
objectId: string
|
||||||
) => Promise<HowLongToBeatCategory[] | null>;
|
) => Promise<HowLongToBeatCategory[] | null>;
|
||||||
getGames: (take?: number, skip?: number) => Promise<CatalogueEntry[]>;
|
getGames: (take?: number, skip?: number) => Promise<CatalogueEntry[]>;
|
||||||
searchGameRepacks: (query: string) => Promise<GameRepack[]>;
|
searchGameRepacks: (query: string) => Promise<GameRepack[]>;
|
||||||
|
@ -97,8 +97,10 @@ export function Sidebar() {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const howLongToBeat =
|
const howLongToBeat = await window.electron.getHowLongToBeat(
|
||||||
await window.electron.getHowLongToBeat(gameTitle);
|
shop,
|
||||||
|
objectId
|
||||||
|
);
|
||||||
|
|
||||||
if (howLongToBeat) {
|
if (howLongToBeat) {
|
||||||
howLongToBeatEntriesTable.add({
|
howLongToBeatEntriesTable.add({
|
||||||
|
@ -45,22 +45,25 @@ export function ProfileContent() {
|
|||||||
return userProfile?.relation?.status === "ACCEPTED";
|
return userProfile?.relation?.status === "ACCEPTED";
|
||||||
}, [userProfile]);
|
}, [userProfile]);
|
||||||
|
|
||||||
const buildUserGameDetailsPath = (game: UserGame) => {
|
const buildUserGameDetailsPath = useCallback(
|
||||||
if (!userProfile?.hasActiveSubscription || game.achievementCount === 0) {
|
(game: UserGame) => {
|
||||||
return buildGameDetailsPath({
|
if (!userProfile?.hasActiveSubscription || game.achievementCount === 0) {
|
||||||
...game,
|
return buildGameDetailsPath({
|
||||||
objectId: game.objectId,
|
...game,
|
||||||
});
|
objectId: game.objectId,
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const userParams = userProfile
|
const userParams = userProfile
|
||||||
? {
|
? {
|
||||||
userId: userProfile.id,
|
userId: userProfile.id,
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return buildGameAchievementPath({ ...game }, userParams);
|
return buildGameAchievementPath({ ...game }, userParams);
|
||||||
};
|
},
|
||||||
|
[userProfile]
|
||||||
|
);
|
||||||
|
|
||||||
const formatPlayTime = useCallback(
|
const formatPlayTime = useCallback(
|
||||||
(playTimeInSeconds = 0) => {
|
(playTimeInSeconds = 0) => {
|
||||||
@ -176,7 +179,7 @@ export function ProfileContent() {
|
|||||||
game.achievementCount > 0 && (
|
game.achievementCount > 0 && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
color: "white",
|
color: "#fff",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
@ -232,6 +235,8 @@ export function ProfileContent() {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
|
minWidth: "100%",
|
||||||
|
minHeight: "100%",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
@ -259,6 +264,7 @@ export function ProfileContent() {
|
|||||||
userStats,
|
userStats,
|
||||||
numberFormatter,
|
numberFormatter,
|
||||||
t,
|
t,
|
||||||
|
buildUserGameDetailsPath,
|
||||||
formatPlayTime,
|
formatPlayTime,
|
||||||
navigate,
|
navigate,
|
||||||
]);
|
]);
|
||||||
|
@ -3,12 +3,3 @@ export interface HowLongToBeatCategory {
|
|||||||
duration: string;
|
duration: string;
|
||||||
accuracy: string;
|
accuracy: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HowLongToBeatResult {
|
|
||||||
game_id: number;
|
|
||||||
game_name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HowLongToBeatSearchResponse {
|
|
||||||
data: HowLongToBeatResult[];
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user