mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-01-23 21:44:55 +03:00
feat: achievement animation
This commit is contained in:
parent
7cddcd8147
commit
7e2d9316f3
@ -107,10 +107,10 @@ export class WindowManager {
|
||||
focusable: false,
|
||||
skipTaskbar: true,
|
||||
frame: false,
|
||||
width: 240,
|
||||
height: 60,
|
||||
x: 25,
|
||||
y: 25,
|
||||
width: 350,
|
||||
height: 104,
|
||||
x: 0,
|
||||
y: 0,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "../preload/index.mjs"),
|
||||
sandbox: false,
|
||||
|
44
src/renderer/src/pages/achievement/achievement.css.ts
Normal file
44
src/renderer/src/pages/achievement/achievement.css.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { recipe } from "@vanilla-extract/recipes";
|
||||
import { vars } from "../../theme.css";
|
||||
import { keyframes, style } from "@vanilla-extract/css";
|
||||
|
||||
const animationIn = keyframes({
|
||||
"0%": { transform: `translateY(-240px)` },
|
||||
"100%": { transform: "translateY(0)" },
|
||||
});
|
||||
|
||||
const animationOut = keyframes({
|
||||
"0%": { transform: `translateY(0)` },
|
||||
"100%": { transform: "translateY(-240px)" },
|
||||
});
|
||||
|
||||
export const container = recipe({
|
||||
base: {
|
||||
marginTop: "24px",
|
||||
marginLeft: "24px",
|
||||
animationDuration: "1.0s",
|
||||
height: "60px",
|
||||
display: "flex",
|
||||
},
|
||||
variants: {
|
||||
closing: {
|
||||
true: {
|
||||
animationName: animationOut,
|
||||
transform: "translateY(-240px)",
|
||||
},
|
||||
false: {
|
||||
animationName: animationIn,
|
||||
transform: "translateY(0)",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const content = style({
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: "8px",
|
||||
alignItems: "center",
|
||||
background: vars.color.background,
|
||||
paddingRight: "8px",
|
||||
});
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import achievementSound from "@renderer/assets/audio/achievement.wav";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { vars } from "@renderer/theme.css";
|
||||
import * as styles from "./achievement.css";
|
||||
|
||||
interface AchievementInfo {
|
||||
displayName: string;
|
||||
@ -11,8 +11,16 @@ interface AchievementInfo {
|
||||
export function Achievement() {
|
||||
const { t } = useTranslation("achievement");
|
||||
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
const [achievements, setAchievements] = useState<AchievementInfo[]>([]);
|
||||
const [currentAchievement, setCurrentAchievement] =
|
||||
useState<AchievementInfo | null>(null);
|
||||
|
||||
const achievementAnimation = useRef(-1);
|
||||
const closingAnimation = useRef(-1);
|
||||
const visibleAnimation = useRef(-1);
|
||||
|
||||
const audio = useMemo(() => {
|
||||
const audio = new Audio(achievementSound);
|
||||
@ -24,11 +32,9 @@ export function Achievement() {
|
||||
useEffect(() => {
|
||||
const unsubscribe = window.electron.onAchievementUnlocked(
|
||||
(_object, _shop, achievements) => {
|
||||
if (!achievements) return;
|
||||
if (!achievements || !achievements.length) return;
|
||||
|
||||
if (achievements.length) {
|
||||
setAchievements((ach) => ach.concat(achievements));
|
||||
}
|
||||
setAchievements((ach) => ach.concat(achievements));
|
||||
|
||||
audio.play();
|
||||
}
|
||||
@ -41,12 +47,37 @@ export function Achievement() {
|
||||
|
||||
const hasAchievementsPending = achievements.length > 0;
|
||||
|
||||
const startAnimateClosing = useCallback(() => {
|
||||
cancelAnimationFrame(closingAnimation.current);
|
||||
cancelAnimationFrame(visibleAnimation.current);
|
||||
cancelAnimationFrame(achievementAnimation.current);
|
||||
|
||||
setIsClosing(true);
|
||||
|
||||
const zero = performance.now();
|
||||
closingAnimation.current = requestAnimationFrame(
|
||||
function animateClosing(time) {
|
||||
if (time - zero <= 1000) {
|
||||
closingAnimation.current = requestAnimationFrame(animateClosing);
|
||||
} else {
|
||||
setIsVisible(false);
|
||||
}
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasAchievementsPending) {
|
||||
setIsClosing(false);
|
||||
setIsVisible(true);
|
||||
|
||||
let zero = performance.now();
|
||||
cancelAnimationFrame(closingAnimation.current);
|
||||
cancelAnimationFrame(visibleAnimation.current);
|
||||
cancelAnimationFrame(achievementAnimation.current);
|
||||
achievementAnimation.current = requestAnimationFrame(
|
||||
function animateLock(time) {
|
||||
if (time - zero > 3000) {
|
||||
if (time - zero > 2500) {
|
||||
zero = performance.now();
|
||||
setAchievements((ach) => ach.slice(1));
|
||||
}
|
||||
@ -54,30 +85,30 @@ export function Achievement() {
|
||||
}
|
||||
);
|
||||
} else {
|
||||
cancelAnimationFrame(achievementAnimation.current);
|
||||
startAnimateClosing();
|
||||
}
|
||||
}, [hasAchievementsPending]);
|
||||
|
||||
if (!hasAchievementsPending) return null;
|
||||
useEffect(() => {
|
||||
if (achievements.length) {
|
||||
setCurrentAchievement(achievements[0]);
|
||||
}
|
||||
}, [achievements]);
|
||||
|
||||
if (!isVisible || !currentAchievement) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: "8px",
|
||||
alignItems: "center",
|
||||
background: vars.color.background,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={achievements[0].iconUrl}
|
||||
alt={achievements[0].displayName}
|
||||
style={{ width: 60, height: 60 }}
|
||||
/>
|
||||
<div>
|
||||
<p>{t("achievement_unlocked")}</p>
|
||||
<p>{achievements[0].displayName}</p>
|
||||
<div className={styles.container({ closing: isClosing })}>
|
||||
<div className={styles.content}>
|
||||
<img
|
||||
src={currentAchievement.iconUrl}
|
||||
alt={currentAchievement.displayName}
|
||||
style={{ flex: 1, width: "60px" }}
|
||||
/>
|
||||
<div>
|
||||
<p>{t("achievement_unlocked")}</p>
|
||||
<p>{currentAchievement.displayName}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user