mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
Merge pull request #1462 from hydralauncher/feat/improve-theming
Feat/improve theming
This commit is contained in:
commit
92ac5b0d1a
4 changed files with 36 additions and 54 deletions
|
@ -57,7 +57,7 @@ export class WindowManager {
|
||||||
trafficLightPosition: { x: 16, y: 16 },
|
trafficLightPosition: { x: 16, y: 16 },
|
||||||
titleBarOverlay: {
|
titleBarOverlay: {
|
||||||
symbolColor: "#DADBE1",
|
symbolColor: "#DADBE1",
|
||||||
color: "#151515",
|
color: "#00000000",
|
||||||
height: 34,
|
height: 34,
|
||||||
},
|
},
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
|
|
|
@ -16,13 +16,8 @@ import { useUserDetails } from "@renderer/hooks";
|
||||||
import { useSubscription } from "@renderer/hooks/use-subscription";
|
import { useSubscription } from "@renderer/hooks/use-subscription";
|
||||||
import "./game-details.scss";
|
import "./game-details.scss";
|
||||||
|
|
||||||
const HERO_HEIGHT = 300;
|
|
||||||
const HERO_ANIMATION_THRESHOLD = 25;
|
|
||||||
|
|
||||||
export function GameDetailsContent() {
|
export function GameDetailsContent() {
|
||||||
const heroRef = useRef<HTMLDivElement | null>(null);
|
const heroRef = useRef<HTMLDivElement | null>(null);
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
const [isHeaderStuck, setIsHeaderStuck] = useState(false);
|
|
||||||
|
|
||||||
const { t } = useTranslation("game_details");
|
const { t } = useTranslation("game_details");
|
||||||
|
|
||||||
|
@ -61,7 +56,7 @@ export function GameDetailsContent() {
|
||||||
return t("no_shop_details");
|
return t("no_shop_details");
|
||||||
}, [shopDetails, t]);
|
}, [shopDetails, t]);
|
||||||
|
|
||||||
const [backdropOpactiy, setBackdropOpacity] = useState(1);
|
const [backdropOpacity, setBackdropOpacity] = useState(1);
|
||||||
|
|
||||||
const handleHeroLoad = async () => {
|
const handleHeroLoad = async () => {
|
||||||
const output = await average(steamUrlBuilder.libraryHero(objectId!), {
|
const output = await average(steamUrlBuilder.libraryHero(objectId!), {
|
||||||
|
@ -80,26 +75,6 @@ export function GameDetailsContent() {
|
||||||
setBackdropOpacity(1);
|
setBackdropOpacity(1);
|
||||||
}, [objectId]);
|
}, [objectId]);
|
||||||
|
|
||||||
const onScroll: React.UIEventHandler<HTMLElement> = (event) => {
|
|
||||||
const heroHeight = heroRef.current?.clientHeight ?? 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scrollY <= heroHeight && isHeaderStuck) {
|
|
||||||
setIsHeaderStuck(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
setBackdropOpacity(opacity);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCloudSaveButtonClick = () => {
|
const handleCloudSaveButtonClick = () => {
|
||||||
if (!userDetails) {
|
if (!userDetails) {
|
||||||
window.electron.openAuthWindow(AuthPage.SignIn);
|
window.electron.openAuthWindow(AuthPage.SignIn);
|
||||||
|
@ -122,31 +97,25 @@ export function GameDetailsContent() {
|
||||||
<div
|
<div
|
||||||
className={`game-details__wrapper ${hasNSFWContentBlocked ? "game-details__wrapper--blurred" : ""}`}
|
className={`game-details__wrapper ${hasNSFWContentBlocked ? "game-details__wrapper--blurred" : ""}`}
|
||||||
>
|
>
|
||||||
<img
|
<section className="game-details__container">
|
||||||
src={steamUrlBuilder.libraryHero(objectId!)}
|
|
||||||
className="game-details__hero-image"
|
|
||||||
alt={game?.title}
|
|
||||||
onLoad={handleHeroLoad}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<section
|
|
||||||
ref={containerRef}
|
|
||||||
onScroll={onScroll}
|
|
||||||
className="game-details__container"
|
|
||||||
>
|
|
||||||
<div ref={heroRef} className="game-details__hero">
|
<div ref={heroRef} className="game-details__hero">
|
||||||
|
<img
|
||||||
|
src={steamUrlBuilder.libraryHero(objectId!)}
|
||||||
|
className="game-details__hero-image"
|
||||||
|
alt={game?.title}
|
||||||
|
onLoad={handleHeroLoad}
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
className="game-details__hero-backdrop"
|
className="game-details__hero-backdrop"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: gameColor,
|
backgroundColor: gameColor,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
opacity: Math.min(1, 1 - backdropOpactiy),
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="game-details__hero-logo-backdrop"
|
className="game-details__hero-logo-backdrop"
|
||||||
style={{ opacity: backdropOpactiy }}
|
style={{ opacity: backdropOpacity }}
|
||||||
>
|
>
|
||||||
<div className="game-details__hero-content">
|
<div className="game-details__hero-content">
|
||||||
<img
|
<img
|
||||||
|
@ -173,7 +142,7 @@ export function GameDetailsContent() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<HeroPanel isHeaderStuck={isHeaderStuck} />
|
<HeroPanel />
|
||||||
|
|
||||||
<div className="game-details__description-container">
|
<div className="game-details__description-container">
|
||||||
<div className="game-details__description-content">
|
<div className="game-details__description-content">
|
||||||
|
|
|
@ -9,11 +9,7 @@ import { HeroPanelPlaytime } from "./hero-panel-playtime";
|
||||||
import { gameDetailsContext } from "@renderer/context";
|
import { gameDetailsContext } from "@renderer/context";
|
||||||
import "./hero-panel.scss";
|
import "./hero-panel.scss";
|
||||||
|
|
||||||
export interface HeroPanelProps {
|
export function HeroPanel() {
|
||||||
isHeaderStuck: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function HeroPanel({ isHeaderStuck }: HeroPanelProps) {
|
|
||||||
const { t } = useTranslation("game_details");
|
const { t } = useTranslation("game_details");
|
||||||
|
|
||||||
const { formatDate } = useDate();
|
const { formatDate } = useDate();
|
||||||
|
@ -54,10 +50,7 @@ export function HeroPanel({ isHeaderStuck }: HeroPanelProps) {
|
||||||
game?.download?.status === "paused";
|
game?.download?.status === "paused";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div style={{ backgroundColor: gameColor }} className="hero-panel">
|
||||||
style={{ backgroundColor: gameColor }}
|
|
||||||
className={`hero-panel ${isHeaderStuck ? "hero-panel--stuck" : ""}`}
|
|
||||||
>
|
|
||||||
<div className="hero-panel__content">{getInfo()}</div>
|
<div className="hero-panel__content">{getInfo()}</div>
|
||||||
<div className="hero-panel__actions">
|
<div className="hero-panel__actions">
|
||||||
<HeroPanelActions />
|
<HeroPanelActions />
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { ThemeActions, ThemeCard, ThemePlaceholder } from "./index";
|
||||||
import type { Theme } from "@types";
|
import type { Theme } from "@types";
|
||||||
import { ImportThemeModal } from "./modals/import-theme-modal";
|
import { ImportThemeModal } from "./modals/import-theme-modal";
|
||||||
import { settingsContext } from "@renderer/context";
|
import { settingsContext } from "@renderer/context";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
interface SettingsAppearanceProps {
|
interface SettingsAppearanceProps {
|
||||||
appearance: {
|
appearance: {
|
||||||
|
@ -24,8 +25,10 @@ export function SettingsAppearance({
|
||||||
authorId: string;
|
authorId: string;
|
||||||
authorName: string;
|
authorName: string;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
const [hasShownModal, setHasShownModal] = useState(false);
|
||||||
|
|
||||||
const { clearTheme } = useContext(settingsContext);
|
const { clearTheme } = useContext(settingsContext);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const loadThemes = useCallback(async () => {
|
const loadThemes = useCallback(async () => {
|
||||||
const themesList = await window.electron.getAllCustomThemes();
|
const themesList = await window.electron.getAllCustomThemes();
|
||||||
|
@ -45,20 +48,37 @@ export function SettingsAppearance({
|
||||||
}, [loadThemes]);
|
}, [loadThemes]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (appearance.theme && appearance.authorId && appearance.authorName) {
|
if (
|
||||||
|
appearance.theme &&
|
||||||
|
appearance.authorId &&
|
||||||
|
appearance.authorName &&
|
||||||
|
!hasShownModal
|
||||||
|
) {
|
||||||
setIsImportThemeModalVisible(true);
|
setIsImportThemeModalVisible(true);
|
||||||
setImportTheme({
|
setImportTheme({
|
||||||
theme: appearance.theme,
|
theme: appearance.theme,
|
||||||
authorId: appearance.authorId,
|
authorId: appearance.authorId,
|
||||||
authorName: appearance.authorName,
|
authorName: appearance.authorName,
|
||||||
});
|
});
|
||||||
|
setHasShownModal(true);
|
||||||
|
|
||||||
|
navigate("/settings", { replace: true });
|
||||||
|
clearTheme();
|
||||||
}
|
}
|
||||||
}, [appearance.theme, appearance.authorId, appearance.authorName]);
|
}, [
|
||||||
|
appearance.theme,
|
||||||
|
appearance.authorId,
|
||||||
|
appearance.authorName,
|
||||||
|
navigate,
|
||||||
|
hasShownModal,
|
||||||
|
clearTheme,
|
||||||
|
]);
|
||||||
|
|
||||||
const onThemeImported = useCallback(() => {
|
const onThemeImported = useCallback(() => {
|
||||||
setIsImportThemeModalVisible(false);
|
setIsImportThemeModalVisible(false);
|
||||||
|
setImportTheme(null);
|
||||||
loadThemes();
|
loadThemes();
|
||||||
}, [clearTheme, loadThemes]);
|
}, [loadThemes]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="settings-appearance">
|
<div className="settings-appearance">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue