feat: starting showing local achievements

This commit is contained in:
Zamitto 2024-09-24 13:56:32 -03:00
parent 5b0cf1e82b
commit 7e3cf0a00e
25 changed files with 102 additions and 12 deletions

View file

@ -5,14 +5,14 @@ import {
OneToOne,
PrimaryGeneratedColumn,
} from "typeorm";
import { Game } from "./game.entity";
import type { Game } from "./game.entity";
@Entity("game_achievement")
export class GameAchievement {
@PrimaryGeneratedColumn()
id: number;
@OneToOne(() => Game)
@OneToOne("Game", "achievements")
@JoinColumn()
game: Game;

View file

@ -12,6 +12,7 @@ import { Repack } from "./repack.entity";
import type { GameShop, GameStatus } from "@types";
import { Downloader } from "@shared";
import type { DownloadQueue } from "./download-queue.entity";
import { GameAchievement } from "./game-achievements.entity";
@Entity("game")
export class Game {
@ -76,6 +77,9 @@ export class Game {
@JoinColumn()
repack: Repack;
@OneToOne("GameAchievement", "game")
achievements: GameAchievement;
@OneToOne("DownloadQueue", "game")
downloadQueue: DownloadQueue;

View file

@ -0,0 +1,46 @@
import type { GameShop } from "@types";
import { registerEvent } from "../register-event";
import { HydraApi } from "@main/services";
import { gameRepository } from "@main/repository";
import { GameAchievement } from "@main/entity";
const getGameAchievements = async (
_event: Electron.IpcMainInvokeEvent,
objectId: string,
shop: GameShop
): Promise<GameAchievement[]> => {
const game = await gameRepository.findOne({
where: { objectID: objectId, shop },
relations: {
achievements: true,
},
});
const gameAchievements = await HydraApi.get(
"/games/achievements",
{ objectId, shop },
{ needsAuth: false }
);
const unlockedAchievements = JSON.parse(
game?.achievements?.unlockedAchievements || "[]"
) as { name: string; unlockTime: number }[];
return gameAchievements.map((achievement) => {
const unlockedAchiement = unlockedAchievements.find((localAchievement) => {
return localAchievement.name == achievement.name;
});
if (unlockedAchiement) {
return {
...achievement,
unlocked: true,
unlockTime: unlockedAchiement.unlockTime * 1000,
};
}
return { ...achievement, unlocked: false, unlockTime: null };
});
};
registerEvent("getGameAchievements", getGameAchievements);

View file

@ -9,13 +9,10 @@ const getGameStats = async (
objectId: string,
shop: GameShop
) => {
const params = new URLSearchParams({
objectId,
shop,
});
const response = await HydraApi.get<GameStats>(
`/games/stats?${params.toString()}`
`/games/stats`,
{ objectId, shop },
{ needsAuth: false }
);
return response;
};

View file

@ -10,6 +10,7 @@ import "./catalogue/search-games";
import "./catalogue/search-game-repacks";
import "./catalogue/get-game-stats";
import "./catalogue/get-trending-games";
import "./catalogue/get-game-achievements";
import "./hardware/get-disk-free-space";
import "./library/add-game-to-library";
import "./library/create-game-shortcut";

View file

@ -67,6 +67,7 @@ const runMigrations = async () => {
);
});
await knexClient.migrate.down(migrationConfig);
await knexClient.migrate.latest(migrationConfig);
await knexClient.destroy();
};

View file

@ -16,7 +16,7 @@ import { publishNewRepacksNotifications } from "./services/notifications";
import { MoreThan } from "typeorm";
import { HydraApi } from "./services/hydra-api";
import { uploadGamesBatch } from "./services/library-sync";
import { saveAllLocalSteamAchivements } from "./events/achievements/services/save-all-local-steam-achivements";
import { saveAllLocalSteamAchivements } from "./services/achievements/services/save-all-local-steam-achivements";
const loadState = async (userPreferences: UserPreferences | null) => {
RepacksManager.updateRepacks();

View file

@ -8,7 +8,7 @@ import { Game } from "@main/entity";
import {
startGameAchievementObserver,
stopGameAchievementObserver,
} from "@main/events/achievements/game-achievements-observer";
} from "@main/services/achievements/game-achievements-observer";
export const gamesPlaytime = new Map<
number,

View file

@ -13,6 +13,7 @@ import type {
UpdateProfileRequest,
} from "@types";
import type { CatalogueCategory } from "@shared";
import { Game } from "@main/entity";
contextBridge.exposeInMainWorld("electron", {
/* Torrenting */
@ -49,6 +50,8 @@ contextBridge.exposeInMainWorld("electron", {
getGameStats: (objectId: string, shop: GameShop) =>
ipcRenderer.invoke("getGameStats", objectId, shop),
getTrendingGames: () => ipcRenderer.invoke("getTrendingGames"),
getGameAchievements: (objectId: string, shop: GameShop) =>
ipcRenderer.invoke("getGameAchievements", objectId, shop),
/* User preferences */
getUserPreferences: () => ipcRenderer.invoke("getUserPreferences"),

View file

@ -7,6 +7,7 @@ import { useAppDispatch, useAppSelector, useDownload } from "@renderer/hooks";
import type {
Game,
GameAchievement,
GameRepack,
GameShop,
GameStats,
@ -53,6 +54,7 @@ export function GameDetailsContextProvider({
const [shopDetails, setShopDetails] = useState<ShopDetails | null>(null);
const [repacks, setRepacks] = useState<GameRepack[]>([]);
const [achievements, setAchievements] = useState<GameAchievement[]>([]);
const [game, setGame] = useState<Game | null>(null);
const [hasNSFWContentBlocked, setHasNSFWContentBlocked] = useState(false);
@ -99,8 +101,9 @@ export function GameDetailsContextProvider({
),
window.electron.searchGameRepacks(gameTitle),
window.electron.getGameStats(objectID!, shop as GameShop),
window.electron.getGameAchievements(objectID!, shop as GameShop),
])
.then(([appDetailsResult, repacksResult, statsResult]) => {
.then(([appDetailsResult, repacksResult, statsResult, achievements]) => {
if (appDetailsResult.status === "fulfilled") {
setShopDetails(appDetailsResult.value);
@ -117,6 +120,11 @@ export function GameDetailsContextProvider({
setRepacks(repacksResult.value);
if (statsResult.status === "fulfilled") setStats(statsResult.value);
if (achievements.status === "fulfilled") {
console.log(achievements.value);
setAchievements(achievements.value);
}
})
.finally(() => {
setIsLoading(false);
@ -193,6 +201,7 @@ export function GameDetailsContextProvider({
showGameOptionsModal,
showRepacksModal,
stats,
achievements,
hasNSFWContentBlocked,
setHasNSFWContentBlocked,
setGameColor,

View file

@ -1,5 +1,6 @@
import type {
Game,
GameAchievement,
GameRepack,
GameShop,
GameStats,
@ -19,6 +20,7 @@ export interface GameDetailsContext {
showRepacksModal: boolean;
showGameOptionsModal: boolean;
stats: GameStats | null;
achievements: GameAchievement[];
hasNSFWContentBlocked: boolean;
setGameColor: React.Dispatch<React.SetStateAction<string>>;
selectGameExecutable: () => Promise<string | null>;

View file

@ -25,6 +25,7 @@ import type {
UserStats,
UserDetails,
FriendRequestSync,
GameAchievement,
} from "@types";
import type { DiskSpace } from "check-disk-space";
@ -65,6 +66,10 @@ declare global {
searchGameRepacks: (query: string) => Promise<GameRepack[]>;
getGameStats: (objectId: string, shop: GameShop) => Promise<GameStats>;
getTrendingGames: () => Promise<TrendingGame[]>;
getGameAchievements: (
objectId: string,
shop: GameShop
) => Promise<GameAchievement[]>;
/* Library */
addGameToLibrary: (

View file

@ -17,7 +17,8 @@ export function Sidebar() {
const [activeRequirement, setActiveRequirement] =
useState<keyof SteamAppDetails["pc_requirements"]>("minimum");
const { gameTitle, shopDetails, stats } = useContext(gameDetailsContext);
const { gameTitle, shopDetails, stats, achievements } =
useContext(gameDetailsContext);
const { t } = useTranslation("game_details");
@ -45,6 +46,17 @@ export function Sidebar() {
isLoading={howLongToBeat.isLoading}
/> */}
{achievements.map((achievement, index) => (
<div key={index}>
<img
src={achievement.unlocked ? achievement.icon : achievement.icongray}
/>
<p>{achievement.displayName}</p>
{achievement.unlockTime &&
new Date(achievement.unlockTime).toDateString()}
</div>
))}
{stats && (
<>
<div

View file

@ -28,6 +28,16 @@ export interface GameRepack {
updatedAt: Date;
}
export interface GameAchievement {
name: string;
displayName: string;
description?: string;
unlocked: boolean;
unlockTime: number | null;
icon: string;
icongray: string;
}
export type ShopDetails = SteamAppDetails & {
objectID: string;
};