mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
feat: skeleton
This commit is contained in:
parent
359733fa40
commit
e7a4888f54
5 changed files with 61 additions and 44 deletions
|
@ -43,7 +43,7 @@ export const gameDetailsContext = createContext<GameDetailsContext>({
|
||||||
showRepacksModal: false,
|
showRepacksModal: false,
|
||||||
showGameOptionsModal: false,
|
showGameOptionsModal: false,
|
||||||
stats: null,
|
stats: null,
|
||||||
achievements: [],
|
achievements: null,
|
||||||
hasNSFWContentBlocked: false,
|
hasNSFWContentBlocked: false,
|
||||||
setGameColor: () => {},
|
setGameColor: () => {},
|
||||||
selectGameExecutable: async () => null,
|
selectGameExecutable: async () => null,
|
||||||
|
@ -70,7 +70,9 @@ export function GameDetailsContextProvider({
|
||||||
shop,
|
shop,
|
||||||
}: GameDetailsContextProps) {
|
}: GameDetailsContextProps) {
|
||||||
const [shopDetails, setShopDetails] = useState<ShopDetails | null>(null);
|
const [shopDetails, setShopDetails] = useState<ShopDetails | null>(null);
|
||||||
const [achievements, setAchievements] = useState<UserAchievement[]>([]);
|
const [achievements, setAchievements] = useState<UserAchievement[] | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
const [game, setGame] = useState<Game | null>(null);
|
const [game, setGame] = useState<Game | null>(null);
|
||||||
const [hasNSFWContentBlocked, setHasNSFWContentBlocked] = useState(false);
|
const [hasNSFWContentBlocked, setHasNSFWContentBlocked] = useState(false);
|
||||||
const abortControllerRef = useRef<AbortController | null>(null);
|
const abortControllerRef = useRef<AbortController | null>(null);
|
||||||
|
@ -176,7 +178,7 @@ export function GameDetailsContextProvider({
|
||||||
setGame(null);
|
setGame(null);
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setisGameRunning(false);
|
setisGameRunning(false);
|
||||||
setAchievements([]);
|
setAchievements(null);
|
||||||
dispatch(setHeaderTitle(gameTitle));
|
dispatch(setHeaderTitle(gameTitle));
|
||||||
}, [objectId, gameTitle, dispatch]);
|
}, [objectId, gameTitle, dispatch]);
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ export interface GameDetailsContext {
|
||||||
showRepacksModal: boolean;
|
showRepacksModal: boolean;
|
||||||
showGameOptionsModal: boolean;
|
showGameOptionsModal: boolean;
|
||||||
stats: GameStats | null;
|
stats: GameStats | null;
|
||||||
achievements: UserAchievement[];
|
achievements: UserAchievement[] | null;
|
||||||
hasNSFWContentBlocked: boolean;
|
hasNSFWContentBlocked: boolean;
|
||||||
setGameColor: React.Dispatch<React.SetStateAction<string>>;
|
setGameColor: React.Dispatch<React.SetStateAction<string>>;
|
||||||
selectGameExecutable: () => Promise<string | null>;
|
selectGameExecutable: () => Promise<string | null>;
|
||||||
|
|
|
@ -8,20 +8,23 @@ import { formatDownloadProgress } from "@renderer/helpers";
|
||||||
import { TrophyIcon } from "@primer/octicons-react";
|
import { TrophyIcon } from "@primer/octicons-react";
|
||||||
import { SPACING_UNIT, vars } from "@renderer/theme.css";
|
import { SPACING_UNIT, vars } from "@renderer/theme.css";
|
||||||
import { gameDetailsContext } from "@renderer/context";
|
import { gameDetailsContext } from "@renderer/context";
|
||||||
import { GameShop, UserAchievement } from "@types";
|
import { UserAchievement } from "@types";
|
||||||
import { average } from "color.js";
|
import { average } from "color.js";
|
||||||
import Color from "color";
|
import Color from "color";
|
||||||
|
|
||||||
const HERO_ANIMATION_THRESHOLD = 25;
|
const HERO_ANIMATION_THRESHOLD = 25;
|
||||||
|
|
||||||
interface AchievementsContentProps {
|
interface AchievementsContentProps {
|
||||||
otherUserId: string | null;
|
otherUser: {
|
||||||
otherUserDisplayName: string | null;
|
userId: string;
|
||||||
|
displayName: string;
|
||||||
|
achievements: UserAchievement[];
|
||||||
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AchievementListProps {
|
interface AchievementListProps {
|
||||||
achievements: UserAchievement[];
|
achievements: UserAchievement[];
|
||||||
otherUserAchievements: UserAchievement[];
|
otherUserAchievements?: UserAchievement[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AchievementPanelProps {
|
interface AchievementPanelProps {
|
||||||
|
@ -100,7 +103,7 @@ function AchievementList({
|
||||||
const { t } = useTranslation("achievement");
|
const { t } = useTranslation("achievement");
|
||||||
const { formatDateTime } = useDate();
|
const { formatDateTime } = useDate();
|
||||||
|
|
||||||
if (otherUserAchievements.length === 0) {
|
if (!otherUserAchievements || otherUserAchievements.length === 0) {
|
||||||
return (
|
return (
|
||||||
<ul className={styles.list}>
|
<ul className={styles.list}>
|
||||||
{achievements.map((achievement, index) => (
|
{achievements.map((achievement, index) => (
|
||||||
|
@ -200,34 +203,28 @@ function AchievementList({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AchievementsContent({
|
export function AchievementsContent({ otherUser }: AchievementsContentProps) {
|
||||||
otherUserId: userId,
|
|
||||||
otherUserDisplayName: displayName,
|
|
||||||
}: AchievementsContentProps) {
|
|
||||||
const heroRef = useRef<HTMLDivElement | null>(null);
|
const heroRef = useRef<HTMLDivElement | null>(null);
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [isHeaderStuck, setIsHeaderStuck] = useState(false);
|
const [isHeaderStuck, setIsHeaderStuck] = useState(false);
|
||||||
const [backdropOpactiy, setBackdropOpacity] = useState(1);
|
const [backdropOpactiy, setBackdropOpacity] = useState(1);
|
||||||
const [otherUserAchievements, setOtherUserAchievements] = useState<
|
|
||||||
UserAchievement[]
|
|
||||||
>([]);
|
|
||||||
|
|
||||||
const { gameTitle, objectId, shop, achievements, gameColor, setGameColor } =
|
const { gameTitle, objectId, shop, achievements, gameColor, setGameColor } =
|
||||||
useContext(gameDetailsContext);
|
useContext(gameDetailsContext);
|
||||||
|
|
||||||
const sortedAchievements = useMemo(() => {
|
const sortedAchievements = useMemo(() => {
|
||||||
if (otherUserAchievements.length === 0) return achievements;
|
if (!otherUser || otherUser.achievements.length === 0) return achievements!;
|
||||||
|
|
||||||
return achievements.sort((a, b) => {
|
return achievements!.sort((a, b) => {
|
||||||
const indexA = otherUserAchievements.findIndex(
|
const indexA = otherUser.achievements.findIndex(
|
||||||
(achievement) => achievement.name === a.name
|
(achievement) => achievement.name === a.name
|
||||||
);
|
);
|
||||||
const indexB = otherUserAchievements.findIndex(
|
const indexB = otherUser.achievements.findIndex(
|
||||||
(achievement) => achievement.name === b.name
|
(achievement) => achievement.name === b.name
|
||||||
);
|
);
|
||||||
return indexA - indexB;
|
return indexA - indexB;
|
||||||
});
|
});
|
||||||
}, [achievements, otherUserAchievements]);
|
}, [achievements, otherUser]);
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
@ -250,16 +247,6 @@ export function AchievementsContent({
|
||||||
setGameColor(backgroundColor);
|
setGameColor(backgroundColor);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (objectId && shop && userId) {
|
|
||||||
window.electron
|
|
||||||
.getGameAchievements(objectId, shop as GameShop, userId)
|
|
||||||
.then((achievements) => {
|
|
||||||
setOtherUserAchievements(achievements);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [objectId, shop, userId]);
|
|
||||||
|
|
||||||
const onScroll: React.UIEventHandler<HTMLElement> = (event) => {
|
const onScroll: React.UIEventHandler<HTMLElement> = (event) => {
|
||||||
const heroHeight = heroRef.current?.clientHeight ?? styles.HERO_HEIGHT;
|
const heroHeight = heroRef.current?.clientHeight ?? styles.HERO_HEIGHT;
|
||||||
|
|
||||||
|
@ -320,10 +307,10 @@ export function AchievementsContent({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.panel({ stuck: isHeaderStuck })}>
|
<div className={styles.panel({ stuck: isHeaderStuck })}>
|
||||||
{userId && (
|
{otherUser && (
|
||||||
<AchievementPanel
|
<AchievementPanel
|
||||||
displayName={displayName}
|
displayName={otherUser.displayName}
|
||||||
achievements={otherUserAchievements}
|
achievements={otherUser.achievements}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -342,7 +329,7 @@ export function AchievementsContent({
|
||||||
>
|
>
|
||||||
<AchievementList
|
<AchievementList
|
||||||
achievements={sortedAchievements}
|
achievements={sortedAchievements}
|
||||||
otherUserAchievements={otherUserAchievements}
|
otherUserAchievements={otherUser?.achievements}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { setHeaderTitle } from "@renderer/features";
|
import { setHeaderTitle } from "@renderer/features";
|
||||||
import { useAppDispatch, useUserDetails } from "@renderer/hooks";
|
import { useAppDispatch, useUserDetails } from "@renderer/hooks";
|
||||||
import type { GameShop } from "@types";
|
import type { GameShop, UserAchievement } from "@types";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
import { vars } from "@renderer/theme.css";
|
import { vars } from "@renderer/theme.css";
|
||||||
import {
|
import {
|
||||||
|
@ -22,6 +22,10 @@ export function Achievement() {
|
||||||
|
|
||||||
const { userDetails } = useUserDetails();
|
const { userDetails } = useUserDetails();
|
||||||
|
|
||||||
|
const [otherUserAchievements, setOtherUserAchievements] = useState<
|
||||||
|
UserAchievement[] | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -30,10 +34,35 @@ export function Achievement() {
|
||||||
}
|
}
|
||||||
}, [dispatch, title]);
|
}, [dispatch, title]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOtherUserAchievements(null);
|
||||||
|
if (userDetails?.id == userId) {
|
||||||
|
setOtherUserAchievements([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (objectId && shop && userId) {
|
||||||
|
window.electron
|
||||||
|
.getGameAchievements(objectId, shop as GameShop, userId)
|
||||||
|
.then((achievements) => {
|
||||||
|
setOtherUserAchievements(achievements);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [objectId, shop, userId]);
|
||||||
|
|
||||||
if (!objectId || !shop || !title) return null;
|
if (!objectId || !shop || !title) return null;
|
||||||
|
|
||||||
const otherUserId = userDetails?.id == userId ? null : userId;
|
const otherUserId = userDetails?.id == userId ? null : userId;
|
||||||
|
|
||||||
|
const otherUser =
|
||||||
|
otherUserId != null
|
||||||
|
? {
|
||||||
|
userId: otherUserId,
|
||||||
|
displayName: displayName || "",
|
||||||
|
achievements: otherUserAchievements || [],
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GameDetailsContextProvider
|
<GameDetailsContextProvider
|
||||||
gameTitle={title}
|
gameTitle={title}
|
||||||
|
@ -41,19 +70,18 @@ export function Achievement() {
|
||||||
objectId={objectId}
|
objectId={objectId}
|
||||||
>
|
>
|
||||||
<GameDetailsContextConsumer>
|
<GameDetailsContextConsumer>
|
||||||
{({ isLoading }) => {
|
{({ isLoading, achievements }) => {
|
||||||
return (
|
return (
|
||||||
<SkeletonTheme
|
<SkeletonTheme
|
||||||
baseColor={vars.color.background}
|
baseColor={vars.color.background}
|
||||||
highlightColor="#444"
|
highlightColor="#444"
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ||
|
||||||
|
achievements === null ||
|
||||||
|
otherUserAchievements === null ? (
|
||||||
<AchievementsSkeleton />
|
<AchievementsSkeleton />
|
||||||
) : (
|
) : (
|
||||||
<AchievementsContent
|
<AchievementsContent otherUser={otherUser} />
|
||||||
otherUserId={otherUserId}
|
|
||||||
otherUserDisplayName={displayName}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</SkeletonTheme>
|
</SkeletonTheme>
|
||||||
);
|
);
|
||||||
|
|
|
@ -148,7 +148,7 @@ export function Sidebar() {
|
||||||
</ul>
|
</ul>
|
||||||
</SidebarSection>
|
</SidebarSection>
|
||||||
)}
|
)}
|
||||||
{userDetails && achievements.length > 0 && (
|
{userDetails && achievements && achievements.length > 0 && (
|
||||||
<SidebarSection
|
<SidebarSection
|
||||||
title={t("achievements_count", {
|
title={t("achievements_count", {
|
||||||
unlockedCount: achievements.filter((a) => a.unlocked).length,
|
unlockedCount: achievements.filter((a) => a.unlocked).length,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue