fix(app): show cached auth splash art without a fade-in flash (#1907)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-24 12:43:26 +02:00
1 parent 79cf57abe4
commit 5eb7822691
4 files changed
+29 -52

No files matched your search

@@ -41,7 +41,7 @@ const AuthLayoutContent = observer(function AuthLayoutContent({children}: {child
const registerFormDraftsRef = useRef<Map<string, AuthRegisterFormDraft>>(new Map());
const scrollerRef = useRef<ScrollerHandle>(null);
const location = useLocation();
const {patternReady, splashLoaded, splashDimensions} = useAuthBackground(splashUrl, foodPatternUrl);
const {patternReady, splashDimensions} = useAuthBackground(splashUrl, foodPatternUrl);
const handleSetSplashUrl = useCallback(
(url: string | null) => {
if (splashUrlRef.current === url) return;
@@ -196,7 +196,6 @@ const AuthLayoutContent = observer(function AuthLayoutContent({children}: {child
>
<AuthBackground
splashUrl={splashUrl}
splashLoaded={splashLoaded}
splashDimensions={splashDimensions}
splashScale={splashScale}
patternReady={patternReady}
@@ -1,10 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import Accessibility from '@app/features/accessibility/state/Accessibility';
import styles from '@app/features/app/components/layout/AuthLayout.module.css';
import {GuildSplashCardAlignment} from '@fluxer/constants/src/GuildConstants';
import type {ValueOf} from '@fluxer/constants/src/ValueOf';
import {motion} from 'framer-motion';
import type React from 'react';
const getSplashAlignmentStyles = (alignment: ValueOf<typeof GuildSplashCardAlignment>) => {
@@ -20,7 +18,6 @@ const getSplashAlignmentStyles = (alignment: ValueOf<typeof GuildSplashCardAlign
export interface AuthBackgroundProps {
splashUrl: string | null;
splashLoaded: boolean;
splashDimensions?: {width: number; height: number} | null;
splashScale?: number | null;
patternReady: boolean;
@@ -32,7 +29,6 @@ export interface AuthBackgroundProps {
export const AuthBackground: React.FC<AuthBackgroundProps> = ({
splashUrl,
splashLoaded,
splashDimensions,
splashScale,
patternReady,
@@ -47,13 +43,7 @@ export const AuthBackground: React.FC<AuthBackgroundProps> = ({
if (useFullCover) {
return (
<div className={className} data-flx="auth.flow.auth-background.div">
<motion.div
initial={{opacity: 0}}
animate={{opacity: splashLoaded ? 1 : 0}}
transition={{duration: Accessibility.useReducedMotion ? 0 : 0.5, ease: 'easeInOut'}}
style={{position: 'absolute', inset: 0}}
data-flx="auth.flow.auth-background.div--2"
>
<div style={{position: 'absolute', inset: 0}} data-flx="auth.flow.auth-background.div--2">
<img
src={splashUrl}
alt=""
@@ -68,17 +58,14 @@ export const AuthBackground: React.FC<AuthBackgroundProps> = ({
data-flx="auth.flow.auth-background.img"
/>
<div className={styles.splashOverlay} data-flx="auth.flow.auth-background.splash-overlay" />
</motion.div>
</div>
</div>
);
}
return (
<div className={styles.rightSplit} data-flx="auth.flow.auth-background.right-split">
<motion.div
<div
className={styles.splashImage}
initial={{opacity: 0}}
animate={{opacity: splashLoaded ? 1 : 0}}
transition={{duration: Accessibility.useReducedMotion ? 0 : 0.5, ease: 'easeInOut'}}
style={{
width: splashDimensions.width,
height: splashDimensions.height,
@@ -104,7 +91,7 @@ export const AuthBackground: React.FC<AuthBackgroundProps> = ({
data-flx="auth.flow.auth-background.img--2"
/>
<div className={styles.splashOverlay} data-flx="auth.flow.auth-background.splash-overlay--2" />
</motion.div>
</div>
</div>
);
}
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import * as ImageCacheUtils from '@app/features/messaging/utils/ImageCacheUtils';
import {useEffect, useState} from 'react';
interface ImageDimensions {
@@ -12,62 +13,53 @@ interface PatternImageLoaderResult {
}
interface SplashImageLoaderResult {
loaded: boolean;
dimensions: ImageDimensions | null;
}
interface AuthBackgroundResult {
patternReady: boolean;
splashLoaded: boolean;
splashDimensions: ImageDimensions | null;
}
export function usePatternImageLoader(patternUrl: string): PatternImageLoaderResult {
const [patternReady, setPatternReady] = useState(false);
const [patternReady, setPatternReady] = useState(() => ImageCacheUtils.hasImage(patternUrl));
useEffect(() => {
const img = new Image();
const handleLoad = () => setPatternReady(true);
img.addEventListener('load', handleLoad, {once: true});
img.src = patternUrl;
return () => img.removeEventListener('load', handleLoad);
if (ImageCacheUtils.hasImage(patternUrl)) {
setPatternReady(true);
return;
}
setPatternReady(false);
return ImageCacheUtils.loadImage(patternUrl, () => setPatternReady(true));
}, [patternUrl]);
return {patternReady};
}
export function useSplashImageLoader(imageUrl: string | null): SplashImageLoaderResult {
const [loaded, setLoaded] = useState(false);
const [dimensions, setDimensions] = useState<ImageDimensions | null>(null);
const [dimensions, setDimensions] = useState<ImageDimensions | null>(
() => ImageCacheUtils.getImageSize(imageUrl) ?? null,
);
useEffect(() => {
if (!imageUrl) {
setLoaded(false);
setDimensions(null);
const cached = ImageCacheUtils.getImageSize(imageUrl);
if (cached) {
setDimensions(cached);
return;
}
let isMounted = true;
const img = new Image();
const handleLoad = () => {
if (!isMounted) return;
setLoaded(true);
setDimensions({
width: img.naturalWidth,
height: img.naturalHeight,
});
};
img.addEventListener('load', handleLoad, {once: true});
img.src = imageUrl;
return () => {
isMounted = false;
};
setDimensions(null);
if (!imageUrl) {
return;
}
return ImageCacheUtils.loadImage(imageUrl, () => {
setDimensions(ImageCacheUtils.getImageSize(imageUrl) ?? null);
});
}, [imageUrl]);
return {loaded, dimensions};
return {dimensions};
}
export function useAuthBackground(splashUrl: string | null, patternUrl: string): AuthBackgroundResult {
const {patternReady} = usePatternImageLoader(patternUrl);
const {loaded: splashLoaded, dimensions: splashDimensions} = useSplashImageLoader(splashUrl);
const {dimensions: splashDimensions} = useSplashImageLoader(splashUrl);
return {
patternReady,
splashLoaded,
splashDimensions,
};
}
@@ -61,7 +61,7 @@ export const InvitePagePreviewModal: React.FC<InvitePagePreviewModalProps> = obs
}
return null;
}, [previewSplashUrl, guild?.id, guild?.splash]);
const {patternReady, splashLoaded, splashDimensions} = useAuthBackground(splashUrl, foodPatternUrl);
const {patternReady, splashDimensions} = useAuthBackground(splashUrl, foodPatternUrl);
const shouldShowSplash = Boolean(splashUrl && splashDimensions);
const handleClose = useCallback(() => {
ModalCommands.pop();
@@ -122,7 +122,6 @@ export const InvitePagePreviewModal: React.FC<InvitePagePreviewModalProps> = obs
<AuthBackground
className={clsx(styles.background, !shouldShowSplash && authLayoutStyles.patternHost)}
splashUrl={splashUrl}
splashLoaded={splashLoaded}
splashDimensions={splashDimensions}
patternReady={patternReady}
patternImageUrl={foodPatternUrl}