From 108b2552b540ca8879aa2e4db2c22b3013f66b94 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:37:44 -0300 Subject: [PATCH 01/12] feat: achievements adjustments --- .../achievements/achievements-content.tsx | 4 +- .../achievements/achievements-skeleton.tsx | 2 +- .../pages/achievements/achievements.css.ts | 26 ++++--- .../profile-content/profile-content.tsx | 69 ++++++++++--------- 4 files changed, 55 insertions(+), 46 deletions(-) diff --git a/src/renderer/src/pages/achievements/achievements-content.tsx b/src/renderer/src/pages/achievements/achievements-content.tsx index 6b2bd1e1..153311ef 100644 --- a/src/renderer/src/pages/achievements/achievements-content.tsx +++ b/src/renderer/src/pages/achievements/achievements-content.tsx @@ -422,7 +422,7 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) { {gameTitle} @@ -431,7 +431,7 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) { onScroll={onScroll} className={styles.container} > -
+
-
+
diff --git a/src/renderer/src/pages/achievements/achievements.css.ts b/src/renderer/src/pages/achievements/achievements.css.ts index 682fd2e5..f84bc7be 100644 --- a/src/renderer/src/pages/achievements/achievements.css.ts +++ b/src/renderer/src/pages/achievements/achievements.css.ts @@ -13,11 +13,11 @@ export const wrapper = style({ transition: "all ease 0.3s", }); -export const header = style({ - display: "flex", +export const hero = style({ + width: "100%", height: `${HERO_HEIGHT}px`, minHeight: `${HERO_HEIGHT}px`, - gap: `${SPACING_UNIT}px`, + display: "flex", flexDirection: "column", position: "relative", transition: "all ease 0.2s", @@ -29,14 +29,22 @@ export const header = style({ }, }); -export const hero = style({ - position: "absolute", - inset: "0", - borderRadius: "4px", - objectFit: "cover", - cursor: "pointer", +export const heroImage = style({ width: "100%", + height: `${HERO_HEIGHT}px`, + minHeight: `${HERO_HEIGHT}px`, + objectFit: "cover", + objectPosition: "top", transition: "all ease 0.2s", + position: "absolute", + zIndex: "0", + "@media": { + "(min-width: 1250px)": { + objectPosition: "center", + height: "350px", + minHeight: "350px", + }, + }, }); export const heroContent = style({ diff --git a/src/renderer/src/pages/profile/profile-content/profile-content.tsx b/src/renderer/src/pages/profile/profile-content/profile-content.tsx index 99e09889..de22313e 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-content.tsx +++ b/src/renderer/src/pages/profile/profile-content/profile-content.tsx @@ -46,7 +46,7 @@ export function ProfileContent() { }, [userProfile]); const buildUserGameDetailsPath = (game: UserGame) => { - if (!userProfile?.hasActiveSubscription) { + if (!userProfile?.hasActiveSubscription || game.achievementCount === 0) { return buildGameDetailsPath({ ...game, objectId: game.objectId, @@ -174,55 +174,56 @@ export function ProfileContent() { {formatPlayTime(game.playTimeInSeconds)} - {userProfile.hasActiveSubscription && ( -
+ {userProfile.hasActiveSubscription && + game.achievementCount > 0 && (
- +
+ + + {game.unlockedAchievementCount} /{" "} + {game.achievementCount} + +
+ - {game.unlockedAchievementCount} /{" "} - {game.achievementCount} + {formatDownloadProgress( + game.unlockedAchievementCount / + game.achievementCount + )}
- - {formatDownloadProgress( + + game.achievementCount + } + className={styles.achievementsProgressBar} + />
- - -
- )} + )}
Date: Wed, 16 Oct 2024 23:06:21 -0300 Subject: [PATCH 02/12] feat: testing new achievements page ui --- .github/workflows/build.yml | 1 - .../events/catalogue/get-game-achievements.ts | 2 +- .../achievements/merge-achievements.ts | 8 +- .../achievements/achievements-content.tsx | 85 +++++++------------ .../pages/achievements/achievements.css.ts | 8 +- .../pages/game-details/sidebar/sidebar.tsx | 6 ++ src/types/index.ts | 1 + 7 files changed, 48 insertions(+), 63 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 91069c4b..908f6c80 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,7 +63,6 @@ jobs: with: name: Build-${{ matrix.os }} path: | - dist/win-unpacked/** dist/*-portable.exe dist/*.zip dist/*.dmg diff --git a/src/main/events/catalogue/get-game-achievements.ts b/src/main/events/catalogue/get-game-achievements.ts index 93e89441..da86f895 100644 --- a/src/main/events/catalogue/get-game-achievements.ts +++ b/src/main/events/catalogue/get-game-achievements.ts @@ -110,7 +110,7 @@ const getAchievementsRemoteUser = async ( ...achievementData, unlocked: false, unlockTime: null, - icon: icongray, + icongray: icongray, } as UserAchievement; }) .sort((a, b) => { diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index c97e8f56..7b03723f 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -64,7 +64,13 @@ export const mergeAchievements = async ( localGameAchievement?.unlockedAchievements || "[]" ).filter((achievement) => achievement.name) as UnlockedAchievement[]; - const newAchievements = achievements + const newAchievementsMap = new Map( + achievements.reverse().map((achievement) => { + return [achievement.name.toUpperCase(), achievement]; + }) + ); + + const newAchievements = [...newAchievementsMap.values()] .filter((achievement) => { return !unlockedAchievements.some((localAchievement) => { return ( diff --git a/src/renderer/src/pages/achievements/achievements-content.tsx b/src/renderer/src/pages/achievements/achievements-content.tsx index 153311ef..1a922a37 100644 --- a/src/renderer/src/pages/achievements/achievements-content.tsx +++ b/src/renderer/src/pages/achievements/achievements-content.tsx @@ -37,6 +37,7 @@ interface AchievementPanelProps { function AchievementPanel({ user, otherUser }: AchievementPanelProps) { const { t } = useTranslation("achievement"); + const { userDetails } = useUserDetails(); const getProfileImage = (imageUrl: string | null | undefined) => { return ( @@ -121,35 +122,29 @@ function AchievementPanel({ user, otherUser }: AchievementPanelProps) { const otherUserTotalAchievementCount = otherUser.achievements.length; return ( - <> -
- {getProfileImage(otherUser.profileImageUrl)} +
+

- {t("user_achievements", { - displayName: otherUser.displayName, - })} + {otherUser.displayName}

@@ -182,26 +177,14 @@ function AchievementPanel({ user, otherUser }: AchievementPanelProps) { className={styles.achievementsProgressBar} />
-
-
- {getProfileImage(user.profileImageUrl)}

- {t("your_achievements")} + {userDetails?.displayName}

- +
{getProfileImage(otherUser.profileImageUrl)}
+
{getProfileImage(user.profileImageUrl)}
+
); } @@ -288,7 +273,7 @@ function AchievementList({
  • - {otherUserAchievement.unlockTime && ( +
    +

    {otherUserAchievement.displayName}

    +

    {otherUserAchievement.description}

    +
    +
    + +
    + {otherUserAchievement.unlockTime ? (
    {t("unlocked_at")}

    {formatDateTime(otherUserAchievement.unlockTime)}

    + ) : ( + "Não desbloqueada" )}
    -
    -

    {otherUserAchievement.displayName}

    -

    {otherUserAchievement.description}

    -
    - -
    - {achievements[index].displayName} - {achievements[index].unlockTime && ( +
    + {achievements[index].unlockTime ? (
    {t("unlocked_at")}

    {formatDateTime(achievements[index].unlockTime)}

    + ) : ( + "Não desbloqueada" )}
  • diff --git a/src/renderer/src/pages/achievements/achievements.css.ts b/src/renderer/src/pages/achievements/achievements.css.ts index f84bc7be..986f809f 100644 --- a/src/renderer/src/pages/achievements/achievements.css.ts +++ b/src/renderer/src/pages/achievements/achievements.css.ts @@ -72,12 +72,10 @@ export const container = style({ export const panel = recipe({ base: { width: "100%", - height: "100px", - minHeight: "100px", - padding: `${SPACING_UNIT * 2}px 0`, + height: "180px", + minHeight: "180px", + padding: `${SPACING_UNIT * 2}px`, backgroundColor: vars.color.darkBackground, - display: "flex", - flexDirection: "row", transition: "all ease 0.2s", borderBottom: `solid 1px ${vars.color.border}`, position: "sticky", diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index 8d1fe8d6..a6a24850 100644 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -24,6 +24,8 @@ const fakeAchievements: UserAchievement[] = [ hidden: false, description: "Chop down your first tree.", icon: "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/105600/0fbb33098c9da39d1d4771d8209afface9c46e81.jpg", + icongray: + "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/105600/0fbb33098c9da39d1d4771d8209afface9c46e81.jpg", unlocked: true, unlockTime: Date.now(), }, @@ -32,6 +34,8 @@ const fakeAchievements: UserAchievement[] = [ name: "", hidden: false, icon: "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/105600/0a6ff6a36670c96ceb4d30cf6fd69d2fdf55f38e.jpg", + icongray: + "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/105600/0a6ff6a36670c96ceb4d30cf6fd69d2fdf55f38e.jpg", unlocked: false, unlockTime: null, }, @@ -40,6 +44,8 @@ const fakeAchievements: UserAchievement[] = [ name: "", hidden: false, icon: "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/105600/2d10311274fe7c92ab25cc29afdca86b019ad472.jpg", + icongray: + "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/105600/2d10311274fe7c92ab25cc29afdca86b019ad472.jpg", unlocked: false, unlockTime: null, }, diff --git a/src/types/index.ts b/src/types/index.ts index eadab11c..58c7191f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -45,6 +45,7 @@ export interface UserAchievement { unlocked: boolean; unlockTime: number | null; icon: string; + icongray: string; } export interface RemoteUnlockedAchievement { From 2700f27d03ff8c869c5b66da429aa36538615ccc Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 18 Oct 2024 01:00:29 -0300 Subject: [PATCH 03/12] feat: redoing page --- .../events/catalogue/get-game-achievements.ts | 2 +- .../achievements/get-game-achievement-data.ts | 4 +- .../achievements/achievements-content.tsx | 74 +++++++++++-------- .../pages/achievements/achievements.css.ts | 8 +- 4 files changed, 50 insertions(+), 38 deletions(-) diff --git a/src/main/events/catalogue/get-game-achievements.ts b/src/main/events/catalogue/get-game-achievements.ts index da86f895..734366c8 100644 --- a/src/main/events/catalogue/get-game-achievements.ts +++ b/src/main/events/catalogue/get-game-achievements.ts @@ -51,7 +51,7 @@ const getAchievementLocalUser = async (shop: string, objectId: string) => { ...achievementData, unlocked: false, unlockTime: null, - icon: icongray, + icongray: icongray, } as UserAchievement; }) .sort((a, b) => { diff --git a/src/main/services/achievements/get-game-achievement-data.ts b/src/main/services/achievements/get-game-achievement-data.ts index 443af59a..552e66b7 100644 --- a/src/main/services/achievements/get-game-achievement-data.ts +++ b/src/main/services/achievements/get-game-achievement-data.ts @@ -42,7 +42,9 @@ export const getGameAchievementData = async ( where: { objectId, shop }, }) .then((gameAchievements) => { - return JSON.parse(gameAchievements?.achievements || "[]"); + return JSON.parse( + gameAchievements?.achievements || "[]" + ) as AchievementData[]; }); }); }; diff --git a/src/renderer/src/pages/achievements/achievements-content.tsx b/src/renderer/src/pages/achievements/achievements-content.tsx index 1a922a37..fbf9da9f 100644 --- a/src/renderer/src/pages/achievements/achievements-content.tsx +++ b/src/renderer/src/pages/achievements/achievements-content.tsx @@ -5,7 +5,7 @@ import { useContext, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import * as styles from "./achievements.css"; import { formatDownloadProgress } from "@renderer/helpers"; -import { PersonIcon, TrophyIcon } from "@primer/octicons-react"; +import { LockIcon, PersonIcon, TrophyIcon } from "@primer/octicons-react"; import { SPACING_UNIT, vars } from "@renderer/theme.css"; import { gameDetailsContext } from "@renderer/context"; import { UserAchievement } from "@types"; @@ -26,8 +26,8 @@ interface AchievementsContentProps { } interface AchievementListProps { - achievements: UserAchievement[]; - otherUserAchievements?: UserAchievement[]; + user: UserInfo; + otherUser: UserInfo | null; } interface AchievementPanelProps { @@ -39,18 +39,6 @@ function AchievementPanel({ user, otherUser }: AchievementPanelProps) { const { t } = useTranslation("achievement"); const { userDetails } = useUserDetails(); - const getProfileImage = (imageUrl: string | null | undefined) => { - return ( -
    - {imageUrl ? ( - {"teste"} - ) : ( - - )} -
    - ); - }; - const userTotalAchievementCount = user.achievements.length; const userUnlockedAchievementCount = user.achievements.filter( (achievement) => achievement.unlocked @@ -63,11 +51,9 @@ function AchievementPanel({ user, otherUser }: AchievementPanelProps) { display: "flex", flexDirection: "row", width: "100%", - padding: `0 ${SPACING_UNIT * 2}px`, gap: `${SPACING_UNIT * 2}px`, }} > - {getProfileImage(user.profileImageUrl)}
    -
    {getProfileImage(otherUser.profileImageUrl)}
    -
    {getProfileImage(user.profileImageUrl)}
    ); } -function AchievementList({ - achievements, - otherUserAchievements, -}: AchievementListProps) { +function AchievementList({ user, otherUser }: AchievementListProps) { + const achievements = user.achievements; + const otherUserAchievements = otherUser?.achievements; + const { t } = useTranslation("achievement"); const { formatDateTime } = useDate(); + const { userDetails } = useUserDetails(); + + const getProfileImage = (imageUrl: string | null | undefined) => { + return ( +
    + {imageUrl ? ( + {"teste"} + ) : ( + + )} +
    + ); + }; + if (!otherUserAchievements || otherUserAchievements.length === 0) { return (
      @@ -273,7 +271,7 @@ function AchievementList({
    • {otherUserAchievement.displayName} {otherUserAchievement.unlockTime ? (
      + {getProfileImage(otherUser.profileImageUrl)} {t("unlocked_at")}

      {formatDateTime(otherUserAchievement.unlockTime)}

      ) : ( - "Não desbloqueada" +
      + +

      Não desbloqueada

      +
      )}
      - {achievements[index].unlockTime ? ( + {userDetails?.subscription && achievements[index].unlockTime ? (
      + {getProfileImage(user.profileImageUrl)} {t("unlocked_at")}

      {formatDateTime(achievements[index].unlockTime)}

      ) : ( - "Não desbloqueada" +
      + +

      Não desbloqueada

      +
      )}
    • @@ -434,7 +440,7 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) { user={{ ...userDetails, userId: userDetails.id, - achievements: achievements!, + achievements: sortedAchievements, }} otherUser={otherUser} /> @@ -448,8 +454,12 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) { }} >
    diff --git a/src/renderer/src/pages/achievements/achievements.css.ts b/src/renderer/src/pages/achievements/achievements.css.ts index 986f809f..0a95e889 100644 --- a/src/renderer/src/pages/achievements/achievements.css.ts +++ b/src/renderer/src/pages/achievements/achievements.css.ts @@ -72,8 +72,8 @@ export const container = style({ export const panel = recipe({ base: { width: "100%", - height: "180px", - minHeight: "180px", + height: "150px", + minHeight: "150px", padding: `${SPACING_UNIT * 2}px`, backgroundColor: vars.color.darkBackground, transition: "all ease 0.2s", @@ -184,8 +184,8 @@ export const listItemSkeleton = style({ }); export const profileAvatar = style({ - height: "65px", - width: "65px", + height: "32px", + width: "32px", borderRadius: "4px", display: "flex", justifyContent: "center", From ab27fd21d78dbec96c19f2a8fec04468c67ed5a4 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 18 Oct 2024 09:52:43 -0300 Subject: [PATCH 04/12] feat: redoing page --- src/renderer/src/hooks/use-user-details.ts | 13 +- .../achievements/achievements-content.tsx | 337 ++++++++---------- .../pages/achievements/achievements.css.ts | 25 +- 3 files changed, 170 insertions(+), 205 deletions(-) diff --git a/src/renderer/src/hooks/use-user-details.ts b/src/renderer/src/hooks/use-user-details.ts index 649d24a4..1872c95d 100644 --- a/src/renderer/src/hooks/use-user-details.ts +++ b/src/renderer/src/hooks/use-user-details.ts @@ -1,4 +1,4 @@ -import { useCallback } from "react"; +import { useCallback, useMemo } from "react"; import { useAppDispatch, useAppSelector } from "./redux"; import { setProfileBackground, @@ -129,7 +129,16 @@ export function useUserDetails() { const unblockUser = (userId: string) => window.electron.unblockUser(userId); - const hasActiveSubscription = userDetails?.subscription?.status === "active"; + const hasActiveSubscription = useMemo(() => { + if (!userDetails?.subscription) { + return false; + } + + return ( + userDetails.subscription.expiresAt == null || + new Date(userDetails.subscription.expiresAt) > new Date() + ); + }, [userDetails]); return { userDetails, diff --git a/src/renderer/src/pages/achievements/achievements-content.tsx b/src/renderer/src/pages/achievements/achievements-content.tsx index fbf9da9f..874368a8 100644 --- a/src/renderer/src/pages/achievements/achievements-content.tsx +++ b/src/renderer/src/pages/achievements/achievements-content.tsx @@ -5,15 +5,19 @@ import { useContext, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import * as styles from "./achievements.css"; import { formatDownloadProgress } from "@renderer/helpers"; -import { LockIcon, PersonIcon, TrophyIcon } from "@primer/octicons-react"; +import { + CheckCircleIcon, + LockIcon, + PersonIcon, + TrophyIcon, + UnlockIcon, +} from "@primer/octicons-react"; import { SPACING_UNIT, vars } from "@renderer/theme.css"; import { gameDetailsContext } from "@renderer/context"; import { UserAchievement } from "@types"; import { average } from "color.js"; import Color from "color"; -const HERO_ANIMATION_THRESHOLD = 25; - interface UserInfo { userId: string; displayName: string; @@ -32,180 +36,85 @@ interface AchievementListProps { interface AchievementPanelProps { user: UserInfo; - otherUser: UserInfo | null; } -function AchievementPanel({ user, otherUser }: AchievementPanelProps) { - const { t } = useTranslation("achievement"); - const { userDetails } = useUserDetails(); +function AchievementPanel({ user }: AchievementPanelProps) { + const { userDetails, hasActiveSubscription } = useUserDetails(); const userTotalAchievementCount = user.achievements.length; const userUnlockedAchievementCount = user.achievements.filter( (achievement) => achievement.unlocked ).length; - if (!otherUser) { + const getProfileImage = (user: UserInfo) => { return ( -
    -
    -

    - {t("your_achievements")} -

    -
    -
    - - - {userUnlockedAchievementCount} / {userTotalAchievementCount} - -
    - - - {formatDownloadProgress( - userUnlockedAchievementCount / userTotalAchievementCount - )} - -
    - + {user.profileImageUrl ? ( + {user.displayName} -
    + ) : ( + + )}
    ); - } + }; - const otherUserUnlockedAchievementCount = otherUser.achievements.filter( - (achievement) => achievement.unlocked - ).length; - const otherUserTotalAchievementCount = otherUser.achievements.length; + if (userDetails?.id == user.userId && !hasActiveSubscription) { + return <>; + } return (
    -
    + {getProfileImage(user)} +
    +

    {user.displayName}

    -

    - {otherUser.displayName} -

    -
    - - - {otherUserUnlockedAchievementCount} /{" "} - {otherUserTotalAchievementCount} - -
    - + - {formatDownloadProgress( - otherUserUnlockedAchievementCount / - otherUserTotalAchievementCount - )} + {userUnlockedAchievementCount} / {userTotalAchievementCount}
    - -
    -
    -

    - {userDetails?.displayName} -

    -
    -
    - - - {userUnlockedAchievementCount} / {userTotalAchievementCount} - -
    - - {formatDownloadProgress( - userUnlockedAchievementCount / userTotalAchievementCount - )} - -
    - + + {formatDownloadProgress( + userUnlockedAchievementCount / userTotalAchievementCount + )} +
    +
    ); @@ -220,18 +129,6 @@ function AchievementList({ user, otherUser }: AchievementListProps) { const { userDetails } = useUserDetails(); - const getProfileImage = (imageUrl: string | null | undefined) => { - return ( -
    - {imageUrl ? ( - {"teste"} - ) : ( - - )} -
    - ); - }; - if (!otherUserAchievements || otherUserAchievements.length === 0) { return (
      @@ -271,7 +168,7 @@ function AchievementList({ user, otherUser }: AchievementListProps) {
    • -
      +
      {otherUserAchievement.unlockTime ? ( -
      - {getProfileImage(otherUser.profileImageUrl)} - {t("unlocked_at")} -

      {formatDateTime(otherUserAchievement.unlockTime)}

      +
      + + {formatDateTime(otherUserAchievement.unlockTime)}
      ) : (
      -

      Não desbloqueada

      )}
      -
      +
      {userDetails?.subscription && achievements[index].unlockTime ? (
      - {getProfileImage(user.profileImageUrl)} - {t("unlocked_at")} +

      {formatDateTime(achievements[index].unlockTime)}

      ) : (
      -

      Não desbloqueada

      )}
      @@ -334,7 +245,6 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) { const heroRef = useRef(null); const containerRef = useRef(null); const [isHeaderStuck, setIsHeaderStuck] = useState(false); - const [backdropOpactiy, setBackdropOpacity] = useState(1); const { gameTitle, objectId, shop, achievements, gameColor, setGameColor } = useContext(gameDetailsContext); @@ -380,11 +290,6 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) { const heroHeight = heroRef.current?.clientHeight ?? styles.HERO_HEIGHT; const scrollY = (event.target as HTMLDivElement).scrollTop; - const opacity = Math.max( - 0, - 1 - scrollY / (heroHeight - HERO_ANIMATION_THRESHOLD) - ); - if (scrollY >= heroHeight && !isHeaderStuck) { setIsHeaderStuck(true); } @@ -392,8 +297,22 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) { if (scrollY <= heroHeight && isHeaderStuck) { setIsHeaderStuck(false); } + }; - setBackdropOpacity(opacity); + const getProfileImage = (user: UserInfo) => { + return ( +
      + {user.profileImageUrl ? ( + {user.displayName} + ) : ( + + )} +
      + ); }; if (!objectId || !shop || !gameTitle || !userDetails) return null; @@ -402,6 +321,7 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) {
      {gameTitle} -
      -
      - -
      +
      +
      + +
      + + + {otherUser && } +
      -
      - -
      + {otherUser && ( +
      +
      +
      +
      {getProfileImage(otherUser)}
      +
      + {getProfileImage({ + ...userDetails, + userId: userDetails.id, + achievements: sortedAchievements, + })} +
      +
      +
      + )} +
      Date: Fri, 18 Oct 2024 12:31:16 -0300 Subject: [PATCH 05/12] feat: adjusting ui --- src/renderer/src/hooks/use-date.ts | 2 +- .../achievements/achievements-content.tsx | 102 ++++++++++-------- .../pages/achievements/achievements.css.ts | 5 +- 3 files changed, 60 insertions(+), 49 deletions(-) diff --git a/src/renderer/src/hooks/use-date.ts b/src/renderer/src/hooks/use-date.ts index 21ed1b34..9486400c 100644 --- a/src/renderer/src/hooks/use-date.ts +++ b/src/renderer/src/hooks/use-date.ts @@ -72,7 +72,7 @@ export function useDate() { const locale = getDateLocale(); return format( date, - locale == enUS ? "MM/dd/yyyy - HH:mm" : "dd/MM/yyyy - HH:mm" + locale == enUS ? "MM/dd/yyyy - HH:mm" : "dd/MM/yyyy HH:mm" ); }, diff --git a/src/renderer/src/pages/achievements/achievements-content.tsx b/src/renderer/src/pages/achievements/achievements-content.tsx index 874368a8..c41f376a 100644 --- a/src/renderer/src/pages/achievements/achievements-content.tsx +++ b/src/renderer/src/pages/achievements/achievements-content.tsx @@ -10,7 +10,6 @@ import { LockIcon, PersonIcon, TrophyIcon, - UnlockIcon, } from "@primer/octicons-react"; import { SPACING_UNIT, vars } from "@renderer/theme.css"; import { gameDetailsContext } from "@renderer/context"; @@ -192,49 +191,55 @@ function AchievementList({ user, otherUser }: AchievementListProps) {
      -
      - {otherUserAchievement.unlockTime ? ( -
      - - {formatDateTime(otherUserAchievement.unlockTime)} -
      - ) : ( -
      - -
      - )} -
      + {otherUserAchievement.unlocked ? ( +
      + + {formatDateTime(otherUserAchievement.unlockTime!)} +
      + ) : ( +
      + +
      + )} -
      - {userDetails?.subscription && achievements[index].unlockTime ? ( -
      - -

      {formatDateTime(achievements[index].unlockTime)}

      -
      - ) : ( -
      - -
      - )} -
      + {userDetails?.subscription && achievements[index].unlocked ? ( +
      + + {formatDateTime(achievements[index].unlockTime!)} +
      + ) : ( +
      + +
      + )}
    • ))}
    @@ -371,17 +376,20 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) {
    {otherUser && ( -
    +
    -
    {getProfileImage(otherUser)}
    -
    +
    + {getProfileImage(otherUser)} +
    +
    {getProfileImage({ ...userDetails, userId: userDetails.id, diff --git a/src/renderer/src/pages/achievements/achievements.css.ts b/src/renderer/src/pages/achievements/achievements.css.ts index 77013ba8..84daa165 100644 --- a/src/renderer/src/pages/achievements/achievements.css.ts +++ b/src/renderer/src/pages/achievements/achievements.css.ts @@ -4,6 +4,7 @@ import { recipe } from "@vanilla-extract/recipes"; export const HERO_HEIGHT = 150; export const LOGO_HEIGHT = 100; +export const LOGO_MAX_WIDTH = 200; export const wrapper = style({ display: "flex", @@ -59,7 +60,9 @@ export const heroContent = style({ }); export const gameLogo = style({ + width: LOGO_MAX_WIDTH, height: LOGO_HEIGHT, + objectFit: "contain", }); export const container = style({ @@ -71,7 +74,7 @@ export const container = style({ zIndex: "1", }); -export const panel = recipe({ +export const tableHeader = recipe({ base: { width: "100%", backgroundColor: vars.color.darkBackground, From 584f725edaa6080be1d36b799d8d3d3ce97040d5 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 19 Oct 2024 01:01:14 -0300 Subject: [PATCH 06/12] feat: adjustments --- .../achievements/achievements-content.tsx | 149 ++++++++++++------ .../pages/achievements/achievements.css.ts | 1 - 2 files changed, 105 insertions(+), 45 deletions(-) diff --git a/src/renderer/src/pages/achievements/achievements-content.tsx b/src/renderer/src/pages/achievements/achievements-content.tsx index c41f376a..d876ff9e 100644 --- a/src/renderer/src/pages/achievements/achievements-content.tsx +++ b/src/renderer/src/pages/achievements/achievements-content.tsx @@ -33,11 +33,12 @@ interface AchievementListProps { otherUser: UserInfo | null; } -interface AchievementPanelProps { +interface AchievementSummaryProps { user: UserInfo; + isComparison?: boolean; } -function AchievementPanel({ user }: AchievementPanelProps) { +function AchievementSummary({ user, isComparison }: AchievementSummaryProps) { const { userDetails, hasActiveSubscription } = useUserDetails(); const userTotalAchievementCount = user.achievements.length; @@ -61,8 +62,55 @@ function AchievementPanel({ user }: AchievementPanelProps) { ); }; - if (userDetails?.id == user.userId && !hasActiveSubscription) { - return <>; + if ( + isComparison && + userDetails?.id == user.userId && + !hasActiveSubscription + ) { + return ( +
    +
    + +

    Assine o HYDRA CLOUD para comparar suas conquistas!!!!

    +
    +
    + {getProfileImage(user)} +

    {user.displayName}

    +
    +
    + ); } return ( @@ -71,6 +119,7 @@ function AchievementPanel({ user }: AchievementPanelProps) { display: "flex", gap: `${SPACING_UNIT * 2}px`, alignItems: "center", + padding: `${SPACING_UNIT}px`, }} > {getProfileImage(user)} @@ -126,7 +175,7 @@ function AchievementList({ user, otherUser }: AchievementListProps) { const { t } = useTranslation("achievement"); const { formatDateTime } = useDate(); - const { userDetails } = useUserDetails(); + const { hasActiveSubscription } = useUserDetails(); if (!otherUserAchievements || otherUserAchievements.length === 0) { return ( @@ -167,7 +216,12 @@ function AchievementList({ user, otherUser }: AchievementListProps) {
  • + {hasActiveSubscription ? ( + achievements[index].unlocked ? ( +
    + + {formatDateTime(achievements[index].unlockTime!)} +
    + ) : ( +
    + +
    + ) + ) : null} + {otherUserAchievement.unlocked ? (
    )} - - {userDetails?.subscription && achievements[index].unlocked ? ( -
    - - {formatDateTime(achievements[index].unlockTime!)} -
    - ) : ( -
    - -
    - )}
  • ))} @@ -270,7 +326,7 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) { const dispatch = useAppDispatch(); - const { userDetails } = useUserDetails(); + const { userDetails, hasActiveSubscription } = useUserDetails(); useEffect(() => { if (gameTitle) { @@ -359,19 +415,20 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) { display: "flex", flexDirection: "column", width: "100%", - gap: `${SPACING_UNIT * 2}px`, - padding: `${SPACING_UNIT * 2}px`, + gap: `${SPACING_UNIT}px`, + padding: `${SPACING_UNIT}px`, }} > - - {otherUser && } + {otherUser && }
    @@ -380,22 +437,26 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) {
    + {hasActiveSubscription && ( +
    + {getProfileImage({ + ...userDetails, + userId: userDetails.id, + achievements: sortedAchievements, + })} +
    + )}
    {getProfileImage(otherUser)}
    -
    - {getProfileImage({ - ...userDetails, - userId: userDetails.id, - achievements: sortedAchievements, - })} -
    )} diff --git a/src/renderer/src/pages/achievements/achievements.css.ts b/src/renderer/src/pages/achievements/achievements.css.ts index 84daa165..ebd63aab 100644 --- a/src/renderer/src/pages/achievements/achievements.css.ts +++ b/src/renderer/src/pages/achievements/achievements.css.ts @@ -52,7 +52,6 @@ export const heroImage = style({ export const heroContent = style({ padding: `${SPACING_UNIT * 2}px`, - height: "100%", width: "100%", display: "flex", justifyContent: "space-between", From bc6d038c5814b54baaa388115121a86af4e6087b Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 19 Oct 2024 11:52:55 -0300 Subject: [PATCH 07/12] feat: add correct text --- src/locales/en/translation.json | 3 ++- src/locales/pt-BR/translation.json | 3 ++- src/locales/pt-PT/translation.json | 3 ++- src/renderer/src/pages/achievements/achievements-content.tsx | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 6e749b3e..a4426d2c 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -339,6 +339,7 @@ "achievement_unlocked": "Achievement unlocked", "user_achievements": "{{displayName}}'s Achievements", "your_achievements": "Your Achievements", - "unlocked_at": "Unlocked at:" + "unlocked_at": "Unlocked at:", + "subscription_needed": "A Hydra Cloud subscription is needed to see this content" } } diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index ba572424..77ebe906 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -341,6 +341,7 @@ "achievement_unlocked": "Conquista desbloqueada", "your_achievements": "Suas Conquistas", "user_achievements": "Conquistas de {{displayName}}", - "unlocked_at": "Desbloqueado em:" + "unlocked_at": "Desbloqueado em:", + "subscription_needed": "Você precisa de uma assinatura Hydra Cloud para visualizar este conteúdo" } } diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index b59db09f..dac181ee 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -283,6 +283,7 @@ }, "achievement": { "achievement_unlocked": "Conquista desbloqueada", - "unlocked_at": "Desbloqueado em:" + "unlocked_at": "Desbloqueado em:", + "subscription_needed": "Você precisa de uma assinatura Hydra Cloud para visualizar este conteúdo" } } diff --git a/src/renderer/src/pages/achievements/achievements-content.tsx b/src/renderer/src/pages/achievements/achievements-content.tsx index d876ff9e..e61cf91d 100644 --- a/src/renderer/src/pages/achievements/achievements-content.tsx +++ b/src/renderer/src/pages/achievements/achievements-content.tsx @@ -39,6 +39,7 @@ interface AchievementSummaryProps { } function AchievementSummary({ user, isComparison }: AchievementSummaryProps) { + const { t } = useTranslation("achievement"); const { userDetails, hasActiveSubscription } = useUserDetails(); const userTotalAchievementCount = user.achievements.length; @@ -94,7 +95,7 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) { }} > -

    Assine o HYDRA CLOUD para comparar suas conquistas!!!!

    +

    {t("subscription_needed")}

    Date: Sat, 19 Oct 2024 13:29:14 -0300 Subject: [PATCH 08/12] fix: headers for auth window --- src/main/services/window-manager.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index eaa05f7a..ecdf64f5 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -80,6 +80,10 @@ export class WindowManager { this.mainWindow.webContents.session.webRequest.onBeforeSendHeaders( (details, callback) => { + if (details.webContentsId !== this.mainWindow?.webContents.id) { + return callback(details); + } + const userAgent = new UserAgent(); callback({ From 89bb099caaf0d94692463202296f1282241967b0 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 19 Oct 2024 13:45:21 -0300 Subject: [PATCH 09/12] feat: add link to game in achievements page --- .github/workflows/build.yml | 2 -- .../achievements/achievements-content.tsx | 24 +++++++++++++------ .../pages/achievements/achievements.css.ts | 4 ++++ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 908f6c80..05066c1c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,6 @@ jobs: uses: actions/setup-node@v4 with: node-version: 20.11.1 - cache: "yarn" - name: Install dependencies run: yarn @@ -27,7 +26,6 @@ jobs: uses: actions/setup-python@v5 with: python-version: 3.9 - cache: "pip" - name: Install dependencies run: pip install -r requirements.txt diff --git a/src/renderer/src/pages/achievements/achievements-content.tsx b/src/renderer/src/pages/achievements/achievements-content.tsx index e61cf91d..09b3d311 100644 --- a/src/renderer/src/pages/achievements/achievements-content.tsx +++ b/src/renderer/src/pages/achievements/achievements-content.tsx @@ -4,7 +4,10 @@ import { steamUrlBuilder } from "@shared"; import { useContext, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import * as styles from "./achievements.css"; -import { formatDownloadProgress } from "@renderer/helpers"; +import { + buildGameDetailsPath, + formatDownloadProgress, +} from "@renderer/helpers"; import { CheckCircleIcon, LockIcon, @@ -16,6 +19,7 @@ import { gameDetailsContext } from "@renderer/context"; import { UserAchievement } from "@types"; import { average } from "color.js"; import Color from "color"; +import { Link } from "@renderer/components"; interface UserInfo { userId: string; @@ -95,7 +99,9 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) { }} > -

    {t("subscription_needed")}

    +

    + {t("subscription_needed")} +

    - {gameTitle} + + {gameTitle} +
    diff --git a/src/renderer/src/pages/achievements/achievements.css.ts b/src/renderer/src/pages/achievements/achievements.css.ts index b6f42ce3..c4b66384 100644 --- a/src/renderer/src/pages/achievements/achievements.css.ts +++ b/src/renderer/src/pages/achievements/achievements.css.ts @@ -62,6 +62,10 @@ export const gameLogo = style({ width: LOGO_MAX_WIDTH, height: LOGO_HEIGHT, objectFit: "contain", + transition: "all ease 0.2s", + ":hover": { + transform: "scale(1.05)", + }, }); export const container = style({ From f0a2bf2f485e32b873791e67b2e1430a81c11d95 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 19 Oct 2024 17:23:26 -0300 Subject: [PATCH 10/12] feat: use new endpoint to get compared achievements --- src/main/events/index.ts | 1 + .../get-compared-unlocked-achievements.ts | 44 ++++ src/preload/index.ts | 11 + src/renderer/src/declaration.d.ts | 6 + src/renderer/src/helpers.ts | 4 +- .../achievements/achievements-content.tsx | 233 +++++------------- .../src/pages/achievements/achievements.tsx | 56 +++-- .../compared-achievement-list.tsx | 110 +++++++++ .../profile-content/profile-content.tsx | 2 - src/types/index.ts | 27 ++ 10 files changed, 291 insertions(+), 203 deletions(-) create mode 100644 src/main/events/user/get-compared-unlocked-achievements.ts create mode 100644 src/renderer/src/pages/achievements/compared-achievement-list.tsx diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 80363e4e..ffdfc354 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -50,6 +50,7 @@ import "./user/unblock-user"; import "./user/get-user-friends"; import "./user/get-user-stats"; import "./user/report-user"; +import "./user/get-compared-unlocked-achievements"; import "./profile/get-friend-requests"; import "./profile/get-me"; import "./profile/undo-friendship"; diff --git a/src/main/events/user/get-compared-unlocked-achievements.ts b/src/main/events/user/get-compared-unlocked-achievements.ts new file mode 100644 index 00000000..8c5c8779 --- /dev/null +++ b/src/main/events/user/get-compared-unlocked-achievements.ts @@ -0,0 +1,44 @@ +import type { ComparedAchievements, GameShop } from "@types"; +import { registerEvent } from "../register-event"; +import { userPreferencesRepository } from "@main/repository"; +import { HydraApi } from "@main/services"; + +const getComparedUnlockedAchievements = async ( + _event: Electron.IpcMainInvokeEvent, + objectId: string, + shop: GameShop, + userId: string +) => { + const userPreferences = await userPreferencesRepository.findOne({ + where: { id: 1 }, + }); + + return HydraApi.get( + `/users/${userId}/games/achievements/compare`, + { + shop, + objectId, + language: userPreferences?.language || "en", + } + ).then((achievements) => { + const sortedAchievements = achievements.achievements.sort((a, b) => { + if (a.otherUserStat.unlocked && !b.otherUserStat.unlocked) return -1; + if (!a.otherUserStat.unlocked && b.otherUserStat.unlocked) return 1; + if (a.otherUserStat.unlocked && b.otherUserStat.unlocked) { + return b.otherUserStat.unlockTime! - a.otherUserStat.unlockTime!; + } + + return Number(a.hidden) - Number(b.hidden); + }); + + return { + ...achievements, + achievements: sortedAchievements, + } as ComparedAchievements; + }); +}; + +registerEvent( + "getComparedUnlockedAchievements", + getComparedUnlockedAchievements +); diff --git a/src/preload/index.ts b/src/preload/index.ts index fc2e5a91..0eadd409 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -259,6 +259,17 @@ contextBridge.exposeInMainWorld("electron", { getUserStats: (userId: string) => ipcRenderer.invoke("getUserStats", userId), reportUser: (userId: string, reason: string, description: string) => ipcRenderer.invoke("reportUser", userId, reason, description), + getComparedUnlockedAchievements: ( + objectId: string, + shop: GameShop, + userId: string + ) => + ipcRenderer.invoke( + "getComparedUnlockedAchievements", + objectId, + shop, + userId + ), /* Auth */ signOut: () => ipcRenderer.invoke("signOut"), diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 91bed316..e6a47959 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -29,6 +29,7 @@ import type { GameArtifact, LudusaviBackup, UserAchievement, + ComparedAchievements, } from "@types"; import type { AxiosProgressEvent } from "axios"; import type { DiskSpace } from "check-disk-space"; @@ -202,6 +203,11 @@ declare global { reason: string, description: string ) => Promise; + getComparedUnlockedAchievements: ( + objectId: string, + shop: GameShop, + userId: string + ) => Promise; /* Profile */ getMe: () => Promise; diff --git a/src/renderer/src/helpers.ts b/src/renderer/src/helpers.ts index d4563330..a241bf47 100644 --- a/src/renderer/src/helpers.ts +++ b/src/renderer/src/helpers.ts @@ -36,15 +36,13 @@ export const buildGameDetailsPath = ( export const buildGameAchievementPath = ( game: { shop: GameShop; objectId: string; title: string }, - user?: { userId: string; displayName: string; profileImageUrl: string | null } + user?: { userId: string } ) => { const searchParams = new URLSearchParams({ title: game.title, shop: game.shop, objectId: game.objectId, userId: user?.userId || "", - displayName: user?.displayName || "", - profileImageUrl: user?.profileImageUrl || "", }); return `/achievements/?${searchParams.toString()}`; diff --git a/src/renderer/src/pages/achievements/achievements-content.tsx b/src/renderer/src/pages/achievements/achievements-content.tsx index 09b3d311..ca60be70 100644 --- a/src/renderer/src/pages/achievements/achievements-content.tsx +++ b/src/renderer/src/pages/achievements/achievements-content.tsx @@ -1,40 +1,37 @@ import { setHeaderTitle } from "@renderer/features"; import { useAppDispatch, useDate, useUserDetails } from "@renderer/hooks"; import { steamUrlBuilder } from "@shared"; -import { useContext, useEffect, useMemo, useRef, useState } from "react"; +import { useContext, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import * as styles from "./achievements.css"; import { buildGameDetailsPath, formatDownloadProgress, } from "@renderer/helpers"; -import { - CheckCircleIcon, - LockIcon, - PersonIcon, - TrophyIcon, -} from "@primer/octicons-react"; +import { LockIcon, PersonIcon, TrophyIcon } from "@primer/octicons-react"; import { SPACING_UNIT, vars } from "@renderer/theme.css"; import { gameDetailsContext } from "@renderer/context"; -import { UserAchievement } from "@types"; +import { ComparedAchievements, UserAchievement } from "@types"; import { average } from "color.js"; import Color from "color"; import { Link } from "@renderer/components"; +import { ComparedAchievementList } from "./compared-achievement-list"; interface UserInfo { userId: string; displayName: string; - achievements: UserAchievement[]; profileImageUrl: string | null; + totalAchievementCount: number; + unlockedAchievementCount: number; } interface AchievementsContentProps { otherUser: UserInfo | null; + comparedAchievements: ComparedAchievements | null; } interface AchievementListProps { - user: UserInfo; - otherUser: UserInfo | null; + achievements: UserAchievement[]; } interface AchievementSummaryProps { @@ -46,11 +43,6 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) { const { t } = useTranslation("achievement"); const { userDetails, hasActiveSubscription } = useUserDetails(); - const userTotalAchievementCount = user.achievements.length; - const userUnlockedAchievementCount = user.achievements.filter( - (achievement) => achievement.unlocked - ).length; - const getProfileImage = (user: UserInfo) => { return (
    @@ -155,19 +147,19 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) { > - {userUnlockedAchievementCount} / {userTotalAchievementCount} + {user.unlockedAchievementCount} / {user.totalAchievementCount}
    {formatDownloadProgress( - userUnlockedAchievementCount / userTotalAchievementCount + user.unlockedAchievementCount / user.totalAchievementCount )}
    @@ -175,132 +167,30 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) { ); } -function AchievementList({ user, otherUser }: AchievementListProps) { - const achievements = user.achievements; - const otherUserAchievements = otherUser?.achievements; - +function AchievementList({ achievements }: AchievementListProps) { const { t } = useTranslation("achievement"); const { formatDateTime } = useDate(); - const { hasActiveSubscription } = useUserDetails(); - - if (!otherUserAchievements || otherUserAchievements.length === 0) { - return ( -
      - {achievements.map((achievement, index) => ( -
    • - {achievement.displayName} -
      -

      {achievement.displayName}

      -

      {achievement.description}

      -
      - {achievement.unlockTime && ( -
      - {t("unlocked_at")} -

      {formatDateTime(achievement.unlockTime)}

      -
      - )} -
    • - ))} -
    - ); - } - return (
      - {otherUserAchievements.map((otherUserAchievement, index) => ( -
    • -
      - {otherUserAchievement.displayName} -
      -

      {otherUserAchievement.displayName}

      -

      {otherUserAchievement.description}

      -
      + {achievements.map((achievement, index) => ( +
    • + {achievement.displayName} +
      +

      {achievement.displayName}

      +

      {achievement.description}

      - - {hasActiveSubscription ? ( - achievements[index].unlocked ? ( -
      - - {formatDateTime(achievements[index].unlockTime!)} -
      - ) : ( -
      - -
      - ) - ) : null} - - {otherUserAchievement.unlocked ? ( -
      - - {formatDateTime(otherUserAchievement.unlockTime!)} -
      - ) : ( -
      - + {achievement.unlockTime && ( +
      + {t("unlocked_at")} +

      {formatDateTime(achievement.unlockTime)}

      )}
    • @@ -309,7 +199,10 @@ function AchievementList({ user, otherUser }: AchievementListProps) { ); } -export function AchievementsContent({ otherUser }: AchievementsContentProps) { +export function AchievementsContent({ + otherUser, + comparedAchievements, +}: AchievementsContentProps) { const heroRef = useRef(null); const containerRef = useRef(null); const [isHeaderStuck, setIsHeaderStuck] = useState(false); @@ -317,20 +210,6 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) { const { gameTitle, objectId, shop, achievements, gameColor, setGameColor } = useContext(gameDetailsContext); - const sortedAchievements = useMemo(() => { - if (!otherUser || otherUser.achievements.length === 0) return achievements!; - - return achievements!.sort((a, b) => { - const indexA = otherUser.achievements.findIndex( - (achievement) => achievement.name === a.name - ); - const indexB = otherUser.achievements.findIndex( - (achievement) => achievement.name === b.name - ); - return indexA - indexB; - }); - }, [achievements, otherUser]); - const dispatch = useAppDispatch(); const { userDetails, hasActiveSubscription } = useUserDetails(); @@ -367,14 +246,17 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) { } }; - const getProfileImage = (user: UserInfo) => { + const getProfileImage = ( + profileImageUrl: string | null, + displayName: string + ) => { return (
      - {user.profileImageUrl ? ( + {profileImageUrl ? ( {user.displayName} ) : ( @@ -434,7 +316,13 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) { user={{ ...userDetails, userId: userDetails.id, - achievements: sortedAchievements, + totalAchievementCount: comparedAchievements + ? comparedAchievements.ownerUser.totalAchievementCount + : achievements!.length, + unlockedAchievementCount: comparedAchievements + ? comparedAchievements.ownerUser.unlockedAchievementCount + : achievements!.filter((achievement) => achievement.unlocked) + .length, }} isComparison={otherUser !== null} /> @@ -458,15 +346,17 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) {
      {hasActiveSubscription && (
      - {getProfileImage({ - ...userDetails, - userId: userDetails.id, - achievements: sortedAchievements, - })} + {getProfileImage( + userDetails.profileImageUrl, + userDetails.displayName + )}
      )}
      - {getProfileImage(otherUser)} + {getProfileImage( + otherUser.profileImageUrl, + otherUser.displayName + )}
    @@ -480,14 +370,11 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) { backgroundColor: vars.color.background, }} > - + {otherUser ? ( + + ) : ( + + )}
    diff --git a/src/renderer/src/pages/achievements/achievements.tsx b/src/renderer/src/pages/achievements/achievements.tsx index a53eaba8..d15ba0b9 100644 --- a/src/renderer/src/pages/achievements/achievements.tsx +++ b/src/renderer/src/pages/achievements/achievements.tsx @@ -1,7 +1,7 @@ import { setHeaderTitle } from "@renderer/features"; import { useAppDispatch, useUserDetails } from "@renderer/hooks"; -import type { GameShop, UserAchievement } from "@types"; -import { useEffect, useState } from "react"; +import type { ComparedAchievements, GameShop } from "@types"; +import { useEffect, useMemo, useState } from "react"; import { useSearchParams } from "react-router-dom"; import { vars } from "@renderer/theme.css"; import { @@ -18,14 +18,11 @@ export default function Achievements() { const shop = searchParams.get("shop"); const title = searchParams.get("title"); const userId = searchParams.get("userId"); - const displayName = searchParams.get("displayName"); - const profileImageUrl = searchParams.get("profileImageUrl"); const { userDetails } = useUserDetails(); - const [otherUserAchievements, setOtherUserAchievements] = useState< - UserAchievement[] | null - >(null); + const [comparedAchievements, setComparedAchievements] = + useState(null); const dispatch = useAppDispatch(); @@ -36,31 +33,34 @@ export default function Achievements() { }, [dispatch, title]); useEffect(() => { - setOtherUserAchievements(null); + setComparedAchievements(null); + if (userDetails?.id == userId) { - setOtherUserAchievements([]); return; } if (objectId && shop && userId) { window.electron - .getGameAchievements(objectId, shop as GameShop, userId) - .then((achievements) => { - setOtherUserAchievements(achievements); - }); + .getComparedUnlockedAchievements(objectId, shop as GameShop, userId) + .then(setComparedAchievements); } }, [objectId, shop, userId]); const otherUserId = userDetails?.id === userId ? null : userId; - const otherUser = otherUserId - ? { - userId: otherUserId, - displayName: displayName || "", - achievements: otherUserAchievements || [], - profileImageUrl: profileImageUrl || "", - } - : null; + const otherUser = useMemo(() => { + if (!otherUserId || !comparedAchievements) return null; + + return { + userId: otherUserId, + displayName: comparedAchievements.otherUser.displayName, + profileImageUrl: comparedAchievements.otherUser.profileImageUrl, + totalAchievementCount: + comparedAchievements.otherUser.totalAchievementCount, + unlockedAchievementCount: + comparedAchievements.otherUser.unlockedAchievementCount, + }; + }, [otherUserId, comparedAchievements]); return ( {({ isLoading, achievements }) => { + const showSkeleton = + isLoading || + achievements === null || + (otherUserId && comparedAchievements === null); + return ( - {isLoading || - achievements === null || - (otherUserId && otherUserAchievements === null) ? ( + {showSkeleton ? ( ) : ( - + )} ); diff --git a/src/renderer/src/pages/achievements/compared-achievement-list.tsx b/src/renderer/src/pages/achievements/compared-achievement-list.tsx new file mode 100644 index 00000000..6c0484c3 --- /dev/null +++ b/src/renderer/src/pages/achievements/compared-achievement-list.tsx @@ -0,0 +1,110 @@ +import type { ComparedAchievements } from "@types"; +import * as styles from "./achievements.css"; +import { CheckCircleIcon, LockIcon } from "@primer/octicons-react"; +import { useDate } from "@renderer/hooks"; +import { SPACING_UNIT } from "@renderer/theme.css"; + +export interface ComparedAchievementListProps { + achievements: ComparedAchievements; +} + +export function ComparedAchievementList({ + achievements, +}: ComparedAchievementListProps) { + const { formatDateTime } = useDate(); + + return ( +
      + {achievements.achievements.map((achievement, index) => ( +
    • +
      + {achievement.displayName} +
      +

      {achievement.displayName}

      +

      {achievement.description}

      +
      +
      + + {achievement.onwerUserStat ? ( + achievement.onwerUserStat.unlocked ? ( +
      + + + {formatDateTime(achievement.onwerUserStat.unlockTime!)} + +
      + ) : ( +
      + +
      + ) + ) : null} + + {achievement.otherUserStat.unlocked ? ( +
      + + + {formatDateTime(achievement.otherUserStat.unlockTime!)} + +
      + ) : ( +
      + +
      + )} +
    • + ))} +
    + ); +} diff --git a/src/renderer/src/pages/profile/profile-content/profile-content.tsx b/src/renderer/src/pages/profile/profile-content/profile-content.tsx index de22313e..a989b81d 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-content.tsx +++ b/src/renderer/src/pages/profile/profile-content/profile-content.tsx @@ -56,8 +56,6 @@ export function ProfileContent() { const userParams = userProfile ? { userId: userProfile.id, - displayName: userProfile.displayName, - profileImageUrl: userProfile.profileImageUrl, } : undefined; diff --git a/src/types/index.ts b/src/types/index.ts index 6da26914..c3a91053 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -342,6 +342,33 @@ export interface GameArtifact { downloadCount: number; } +export interface ComparedAchievements { + ownerUser: { + totalAchievementCount: number; + unlockedAchievementCount: number; + }; + otherUser: { + displayName: string; + profileImageUrl: string; + totalAchievementCount: number; + unlockedAchievementCount: number; + }; + achievements: { + hidden: boolean; + icon: string; + displayName: string; + description: string; + onwerUserStat?: { + unlocked: boolean; + unlockTime: number; + }; + otherUserStat: { + unlocked: boolean; + unlockTime: number; + }; + }[]; +} + export * from "./steam.types"; export * from "./real-debrid.types"; export * from "./ludusavi.types"; From 993b35cf3bdc3433963c6800b695959f055247c4 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 20 Oct 2024 12:48:04 -0300 Subject: [PATCH 11/12] feat: adjust rld achievement --- .../achievements/merge-achievements.ts | 2 +- .../achievements/parse-achievement-file.ts | 26 ++++++++++++------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index 7b03723f..367a5550 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -120,7 +120,7 @@ export const mergeAchievements = async ( const mergedLocalAchievements = unlockedAchievements.concat(newAchievements); - if (game?.remoteId) { + if (game.remoteId) { return HydraApi.put( "/profile/games/achievements", { diff --git a/src/main/services/achievements/parse-achievement-file.ts b/src/main/services/achievements/parse-achievement-file.ts index 9c875a3f..eb6321a3 100644 --- a/src/main/services/achievements/parse-achievement-file.ts +++ b/src/main/services/achievements/parse-achievement-file.ts @@ -242,15 +242,23 @@ const processRld = (unlockedAchievements: any): UnlockedAchievement[] => { const unlockedAchievement = unlockedAchievements[achievement]; if (unlockedAchievement?.State) { - newUnlockedAchievements.push({ - name: achievement, - unlockTime: - new DataView( - new Uint8Array( - Buffer.from(unlockedAchievement.Time.toString(), "hex") - ).buffer - ).getUint32(0, true) * 1000, - }); + const unlocked = new DataView( + new Uint8Array( + Buffer.from(unlockedAchievement.State.toString(), "hex") + ).buffer + ).getUint32(0, true); + + if (unlocked === 1) { + newUnlockedAchievements.push({ + name: achievement, + unlockTime: + new DataView( + new Uint8Array( + Buffer.from(unlockedAchievement.Time.toString(), "hex") + ).buffer + ).getUint32(0, true) * 1000, + }); + } } } From 1d7858438d1dfe768422a8cf4ed958adba26ccae Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 20 Oct 2024 12:55:08 -0300 Subject: [PATCH 12/12] feat: update logs for achievements --- .../services/achievements/achievement-watcher.ts | 15 ++++++++++----- .../update-local-unlocked-achivements.ts | 14 +++++++------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/main/services/achievements/achievement-watcher.ts b/src/main/services/achievements/achievement-watcher.ts index d625a927..269e85e9 100644 --- a/src/main/services/achievements/achievement-watcher.ts +++ b/src/main/services/achievements/achievement-watcher.ts @@ -9,7 +9,7 @@ import { getAlternativeObjectIds, } from "./find-achivement-files"; import type { AchievementFile } from "@types"; -import { achievementsLogger, logger } from "../logger"; +import { achievementsLogger } from "../logger"; import { Cracker } from "@shared"; const fileStats: Map = new Map(); @@ -55,8 +55,6 @@ const processAchievementFileDiff = async ( ) => { const unlockedAchievements = parseAchievementFile(file.filePath, file.type); - logger.log("Achievements from file", file.filePath, unlockedAchievements); - if (unlockedAchievements.length) { return mergeAchievements( game.objectID, @@ -80,7 +78,7 @@ const compareFltFolder = async (game: Game, file: AchievementFile) => { return; } - logger.log("Detected change in FLT folder", file.filePath); + achievementsLogger.log("Detected change in FLT folder", file.filePath); await processAchievementFileDiff(game, file); } catch (err) { achievementsLogger.error(err); @@ -101,6 +99,13 @@ const compareFile = async (game: Game, file: AchievementFile) => { if (!previousStat) { if (currentStat.mtimeMs) { + achievementsLogger.log( + "First change in file", + file.filePath, + previousStat, + currentStat.mtimeMs + ); + await processAchievementFileDiff(game, file); return; } @@ -110,7 +115,7 @@ const compareFile = async (game: Game, file: AchievementFile) => { return; } - logger.log( + achievementsLogger.log( "Detected change in file", file.filePath, previousStat, diff --git a/src/main/services/achievements/update-local-unlocked-achivements.ts b/src/main/services/achievements/update-local-unlocked-achivements.ts index a11c3487..38c1fa2d 100644 --- a/src/main/services/achievements/update-local-unlocked-achivements.ts +++ b/src/main/services/achievements/update-local-unlocked-achivements.ts @@ -49,14 +49,14 @@ export const updateAllLocalUnlockedAchievements = async () => { if (parsedAchievements.length) { unlockedAchievements.push(...parsedAchievements); - } - achievementsLogger.log( - "Achievement file for", - game.title, - achievementFile.filePath, - parsedAchievements - ); + achievementsLogger.log( + "Achievement file for", + game.title, + achievementFile.filePath, + parsedAchievements + ); + } } mergeAchievements(game.objectID, "steam", unlockedAchievements, false);