mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-01-23 21:44:55 +03:00
feat: achievement section for user not logged in
This commit is contained in:
parent
a064958d4c
commit
a4475d2145
@ -131,7 +131,8 @@
|
||||
"executable_path_in_use": "Executable already in use by \"{{game}}\"",
|
||||
"warning": "Warning:",
|
||||
"hydra_needs_to_remain_open": "for this download, Hydra needs to remain open util its conclusion. In case Hydra closes before the conclusion, you will lose your progress.",
|
||||
"achievements": "Achievements {{unlockedCount}}/{{achievementsCount}}",
|
||||
"achievements": "Achievements",
|
||||
"achievements_count": "Achievements {{unlockedCount}}/{{achievementsCount}}",
|
||||
"cloud_save": "Cloud save",
|
||||
"cloud_save_description": "Save your progress in the cloud and continue playing on any device",
|
||||
"backups": "Backups",
|
||||
@ -146,7 +147,8 @@
|
||||
"backup_uploaded": "Backup uploaded",
|
||||
"backup_deleted": "Backup deleted",
|
||||
"backup_restored": "Backup restored",
|
||||
"see_all_achievements": "See all achievements"
|
||||
"see_all_achievements": "See all achievements",
|
||||
"sign_in_to_see_achievements": "Sign in to see achievements"
|
||||
},
|
||||
"activation": {
|
||||
"title": "Activate Hydra",
|
||||
|
@ -127,7 +127,8 @@
|
||||
"executable_path_in_use": "Executável em uso por \"{{game}}\"",
|
||||
"warning": "Aviso:",
|
||||
"hydra_needs_to_remain_open": "para este download, o Hydra precisa ficar aberto até a conclusão. Caso o Hydra encerre antes da conclusão, perderá seu progresso.",
|
||||
"achievements": "Conquistas ({{unlockedCount}}/{{achievementsCount}})",
|
||||
"achievements": "Conquistas",
|
||||
"achievements_count": "Conquistas ({{unlockedCount}}/{{achievementsCount}})",
|
||||
"cloud_save": "Salvamento em nuvem",
|
||||
"cloud_save_description": "Matenha seu progresso na nuvem e continue de onde parou em qualquer dispositivo",
|
||||
"backups": "Backups",
|
||||
@ -142,7 +143,8 @@
|
||||
"backup_uploaded": "Backup criado",
|
||||
"backup_deleted": "Backup apagado",
|
||||
"backup_restored": "Backup restaurado",
|
||||
"see_all_achievements": "Ver todas as conquistas"
|
||||
"see_all_achievements": "Ver todas as conquistas",
|
||||
"sign_in_to_see_achievements": "Faça login para ver as conquistas"
|
||||
},
|
||||
"activation": {
|
||||
"title": "Ativação",
|
||||
|
@ -116,7 +116,8 @@
|
||||
"executable_path_in_use": "Executável em uso por \"{{game}}\"",
|
||||
"warning": "Aviso:",
|
||||
"hydra_needs_to_remain_open": "para este download, o Hydra precisa ficar aberto até a conclusão. Caso o Hydra encerre antes da conclusão, perderá seu progresso.",
|
||||
"achievements": "Conquistas ({{unlockedCount}}/{{achievementsCount}})",
|
||||
"achievements": "Conquistas",
|
||||
"achievements_count": "Conquistas ({{unlockedCount}}/{{achievementsCount}})",
|
||||
"see_all_achievements": "Ver todas as conquistas"
|
||||
},
|
||||
"activation": {
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
} from "@main/repository";
|
||||
import { HydraApi } from "../hydra-api";
|
||||
import { AchievementData } from "@types";
|
||||
import { UserNotLoggedInError } from "@shared";
|
||||
|
||||
export const getGameAchievementData = async (
|
||||
objectId: string,
|
||||
@ -30,7 +31,11 @@ export const getGameAchievementData = async (
|
||||
|
||||
return achievements;
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((err) => {
|
||||
if (err instanceof UserNotLoggedInError) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return gameAchievementRepository
|
||||
.findOne({
|
||||
where: { objectId, shop },
|
||||
|
@ -3,12 +3,18 @@ import {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
import { setHeaderTitle } from "@renderer/features";
|
||||
import { getSteamLanguage } from "@renderer/helpers";
|
||||
import { useAppDispatch, useAppSelector, useDownload } from "@renderer/hooks";
|
||||
import {
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
useDownload,
|
||||
useUserDetails,
|
||||
} from "@renderer/hooks";
|
||||
|
||||
import type {
|
||||
Game,
|
||||
@ -67,6 +73,7 @@ export function GameDetailsContextProvider({
|
||||
const [achievements, setAchievements] = useState<UserAchievement[]>([]);
|
||||
const [game, setGame] = useState<Game | null>(null);
|
||||
const [hasNSFWContentBlocked, setHasNSFWContentBlocked] = useState(false);
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
|
||||
const [stats, setStats] = useState<GameStats | null>(null);
|
||||
|
||||
@ -93,6 +100,7 @@ export function GameDetailsContextProvider({
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { lastPacket } = useDownload();
|
||||
const { userDetails } = useUserDetails();
|
||||
|
||||
const userPreferences = useAppSelector(
|
||||
(state) => state.userPreferences.value
|
||||
@ -111,6 +119,10 @@ export function GameDetailsContextProvider({
|
||||
}, [updateGame, isGameDownloading, lastPacket?.game.status]);
|
||||
|
||||
useEffect(() => {
|
||||
if (abortControllerRef.current) abortControllerRef.current.abort();
|
||||
const abortController = new AbortController();
|
||||
abortControllerRef.current = abortController;
|
||||
|
||||
window.electron
|
||||
.getGameShopDetails(
|
||||
objectId!,
|
||||
@ -118,6 +130,8 @@ export function GameDetailsContextProvider({
|
||||
getSteamLanguage(i18n.language)
|
||||
)
|
||||
.then((result) => {
|
||||
if (abortController.signal.aborted) return;
|
||||
|
||||
setShopDetails(result);
|
||||
|
||||
if (
|
||||
@ -133,21 +147,29 @@ export function GameDetailsContextProvider({
|
||||
});
|
||||
|
||||
window.electron.getGameStats(objectId, shop as GameShop).then((result) => {
|
||||
if (abortController.signal.aborted) return;
|
||||
setStats(result);
|
||||
});
|
||||
|
||||
window.electron
|
||||
.getGameAchievements(objectId, shop as GameShop)
|
||||
.then((achievements) => {
|
||||
// TODO: race condition
|
||||
if (abortController.signal.aborted) return;
|
||||
if (!userDetails) return;
|
||||
setAchievements(achievements);
|
||||
})
|
||||
.catch(() => {
|
||||
// TODO: handle user not logged in error
|
||||
});
|
||||
.catch(() => {});
|
||||
|
||||
updateGame();
|
||||
}, [updateGame, dispatch, gameTitle, objectId, shop, i18n.language]);
|
||||
}, [
|
||||
updateGame,
|
||||
dispatch,
|
||||
gameTitle,
|
||||
objectId,
|
||||
shop,
|
||||
i18n.language,
|
||||
userDetails,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
setShopDetails(null);
|
||||
@ -180,6 +202,7 @@ export function GameDetailsContextProvider({
|
||||
objectId,
|
||||
shop,
|
||||
(achievements) => {
|
||||
if (!userDetails) return;
|
||||
setAchievements(achievements);
|
||||
}
|
||||
);
|
||||
@ -187,7 +210,7 @@ export function GameDetailsContextProvider({
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [objectId, shop]);
|
||||
}, [objectId, shop, userDetails]);
|
||||
|
||||
const getDownloadsPath = async () => {
|
||||
if (userPreferences?.downloadsPath) return userPreferences.downloadsPath;
|
||||
|
@ -29,6 +29,7 @@ export function SidebarSection({ title, children }: SidebarSectionProps) {
|
||||
maxHeight: isOpen ? `${content.current?.scrollHeight}px` : "0",
|
||||
overflow: "hidden",
|
||||
transition: "max-height 0.4s cubic-bezier(0, 1, 0, 1)",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@ -1,16 +1,49 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import type { HowLongToBeatCategory, SteamAppDetails } from "@types";
|
||||
import type {
|
||||
HowLongToBeatCategory,
|
||||
SteamAppDetails,
|
||||
UserAchievement,
|
||||
} from "@types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Link } from "@renderer/components";
|
||||
|
||||
import * as styles from "./sidebar.css";
|
||||
import { gameDetailsContext } from "@renderer/context";
|
||||
import { useDate, useFormat } from "@renderer/hooks";
|
||||
import { DownloadIcon, PeopleIcon } from "@primer/octicons-react";
|
||||
import { useDate, useFormat, useUserDetails } from "@renderer/hooks";
|
||||
import { DownloadIcon, LockIcon, PeopleIcon } from "@primer/octicons-react";
|
||||
import { HowLongToBeatSection } from "./how-long-to-beat-section";
|
||||
import { howLongToBeatEntriesTable } from "@renderer/dexie";
|
||||
import { SidebarSection } from "../sidebar-section/sidebar-section";
|
||||
import { buildGameAchievementPath } from "@renderer/helpers";
|
||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||
|
||||
const fakeAchievements: UserAchievement[] = [
|
||||
{
|
||||
displayName: "Timber!!",
|
||||
name: "",
|
||||
hidden: false,
|
||||
description: "Chop down your first tree.",
|
||||
icon: "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/105600/0fbb33098c9da39d1d4771d8209afface9c46e81.jpg",
|
||||
unlocked: true,
|
||||
unlockTime: Date.now(),
|
||||
},
|
||||
{
|
||||
displayName: "Supreme Helper Minion!",
|
||||
name: "",
|
||||
hidden: false,
|
||||
icon: "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/105600/0a6ff6a36670c96ceb4d30cf6fd69d2fdf55f38e.jpg",
|
||||
unlocked: false,
|
||||
unlockTime: null,
|
||||
},
|
||||
{
|
||||
displayName: "Feast of Midas",
|
||||
name: "",
|
||||
hidden: false,
|
||||
icon: "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/105600/2d10311274fe7c92ab25cc29afdca86b019ad472.jpg",
|
||||
unlocked: false,
|
||||
unlockTime: null,
|
||||
},
|
||||
];
|
||||
|
||||
export function Sidebar() {
|
||||
const [howLongToBeat, setHowLongToBeat] = useState<{
|
||||
@ -18,6 +51,8 @@ export function Sidebar() {
|
||||
data: HowLongToBeatCategory[] | null;
|
||||
}>({ isLoading: true, data: null });
|
||||
|
||||
const { userDetails } = useUserDetails();
|
||||
|
||||
const [activeRequirement, setActiveRequirement] =
|
||||
useState<keyof SteamAppDetails["pc_requirements"]>("minimum");
|
||||
|
||||
@ -68,9 +103,53 @@ export function Sidebar() {
|
||||
|
||||
return (
|
||||
<aside className={styles.contentSidebar}>
|
||||
{achievements.length > 0 && (
|
||||
{userDetails === null && (
|
||||
<SidebarSection title={t("achievements")}>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
zIndex: 2,
|
||||
inset: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
background: "rgba(0, 0, 0, 0.7)",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
}}
|
||||
>
|
||||
<LockIcon size={36} />
|
||||
<h3>{t("sign_in_to_see_achievements")}</h3>
|
||||
</div>
|
||||
<ul className={styles.list} style={{ filter: "blur(4px)" }}>
|
||||
{fakeAchievements.map((achievement, index) => (
|
||||
<li key={index}>
|
||||
<div className={styles.listItem}>
|
||||
<img
|
||||
style={{ filter: "blur(8px)" }}
|
||||
className={styles.listItemImage({
|
||||
unlocked: achievement.unlocked,
|
||||
})}
|
||||
src={achievement.icon}
|
||||
alt={achievement.displayName}
|
||||
/>
|
||||
<div>
|
||||
<p>{achievement.displayName}</p>
|
||||
<small>
|
||||
{achievement.unlockTime && format(achievement.unlockTime)}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</SidebarSection>
|
||||
)}
|
||||
{userDetails && achievements.length > 0 && (
|
||||
<SidebarSection
|
||||
title={t("achievements", {
|
||||
title={t("achievements_count", {
|
||||
unlockedCount: achievements.filter((a) => a.unlocked).length,
|
||||
achievementsCount: achievements.length,
|
||||
})}
|
||||
@ -93,7 +172,6 @@ export function Sidebar() {
|
||||
})}
|
||||
src={achievement.icon}
|
||||
alt={achievement.displayName}
|
||||
loading="lazy"
|
||||
/>
|
||||
<div>
|
||||
<p>{achievement.displayName}</p>
|
||||
|
Loading…
Reference in New Issue
Block a user