From 051cc5b35c8687dc2f0a75f29ef7a4a1a93ffb88 Mon Sep 17 00:00:00 2001 From: Maxim Grigorev Date: Fri, 16 May 2025 22:47:59 +0700 Subject: [PATCH] fix: optimized splashText working process --- config.json | 1 + src/appConfig.ts | 1 + src/react/MainMenu.tsx | 46 +++++++++++++++++++++++++++------ src/utils/splashText.ts | 57 ++++++++++++++++++++++++++++++++++++----- 4 files changed, 91 insertions(+), 14 deletions(-) diff --git a/config.json b/config.json index 0515aa40..e45b4052 100644 --- a/config.json +++ b/config.json @@ -26,6 +26,7 @@ } ], "rightSideText": "A Minecraft client clone in the browser!", + "splashTextFallback": "GEN is cooking", "splashText": "https://jsonplaceholder.typicode.com/posts/1", "pauseLinks": [ [ diff --git a/src/appConfig.ts b/src/appConfig.ts index 156c5974..769ef14d 100644 --- a/src/appConfig.ts +++ b/src/appConfig.ts @@ -24,6 +24,7 @@ export type AppConfig = { // hideSettings?: Record allowAutoConnect?: boolean splashText?: string + splashTextFallback?: string pauseLinks?: Array>> keybindings?: Record defaultLanguage?: string diff --git a/src/react/MainMenu.tsx b/src/react/MainMenu.tsx index 86e2b1ea..9f5d5357 100644 --- a/src/react/MainMenu.tsx +++ b/src/react/MainMenu.tsx @@ -4,7 +4,15 @@ import { useSnapshot } from 'valtio' import { haveDirectoryPicker } from '../utils' import { ConnectOptions } from '../connect' import { miscUiState } from '../globalState' -import { isRemoteSplashText, loadRemoteSplashText } from '../utils/splashText' +import { + isRemoteSplashText, + loadRemoteSplashText, + getDisplayText, + cacheSplashText, + hasSourceUrlChanged, + cacheSourceUrl, + clearSplashCache +} from '../utils/splashText' import styles from './mainMenu.module.css' import Button from './Button' import ButtonWithTooltip from './ButtonWithTooltip' @@ -48,24 +56,46 @@ export default ({ singleplayerAvailable = true }: Props) => { const { appConfig } = useSnapshot(miscUiState) - const [splashText, setSplashText] = useState(appConfig?.splashText || '') + + useEffect(() => { + if (hasSourceUrlChanged(appConfig?.splashText)) { + clearSplashCache() + if (appConfig?.splashText && isRemoteSplashText(appConfig.splashText)) { + cacheSourceUrl(appConfig.splashText) + } + } + }, [appConfig?.splashText]) + + const initialSplashText = getDisplayText(appConfig?.splashText, appConfig?.splashTextFallback) + const [splashText, setSplashText] = useState(initialSplashText) + const [showSplash, setShowSplash] = useState(false) + + useEffect(() => { + const timer = setTimeout(() => { + setShowSplash(true) + }, 100) + return () => clearTimeout(timer) + }, []) useEffect(() => { const loadSplashText = async () => { try { if (appConfig?.splashText && isRemoteSplashText(appConfig.splashText)) { const text = await loadRemoteSplashText(appConfig.splashText) - setSplashText(text) - } else { - setSplashText(appConfig?.splashText || '') + + if (text && text.trim() !== '') { + setSplashText(text) + cacheSplashText(text) + } + } else if (appConfig?.splashText && !isRemoteSplashText(appConfig.splashText)) { + setSplashText(appConfig.splashText) } } catch (error) { console.error('Failed to load splash text:', error) - setSplashText('Error loading splash text') } } void loadSplashText() - }, [appConfig?.splashText]) + }, [appConfig?.splashText, appConfig?.splashTextFallback]) if (!bottomRightLinks?.trim()) bottomRightLinks = undefined // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion @@ -112,7 +142,7 @@ export default ({
- {splashText} + {showSplash && {splashText}}
diff --git a/src/utils/splashText.ts b/src/utils/splashText.ts index 9ebe157d..2332c22a 100644 --- a/src/utils/splashText.ts +++ b/src/utils/splashText.ts @@ -1,17 +1,15 @@ const MAX_WORDS = 5 const HTTPS_REGEX = /^https?:\/\/[-\w@:%.+~#=]{1,256}\.[a-zA-Z\d()]{1,6}\b([-\w()@:%+.~#?&/=]*)$/ const TIMEOUT_MS = 5000 - -const sanitizeText = (text: string): string => { - return text.replaceAll(/<[^>]*>/g, '').replaceAll(/[^\w\s.,!?-]/g, '') -} +const SPLASH_CACHE_KEY = 'minecraft_splash_text_cache' +const SPLASH_URL_KEY = 'minecraft_splash_url' const limitWords = (text: string): string => { const words = text.split(/\s+/) if (words.length <= MAX_WORDS) { - return sanitizeText(text) + return text } - return sanitizeText(words.slice(0, MAX_WORDS).join(' ') + '...') + return words.slice(0, MAX_WORDS).join(' ') + '...' } export const isRemoteSplashText = (text: string): boolean => { @@ -50,3 +48,50 @@ export const loadRemoteSplashText = async (url: string): Promise => { return 'Failed to load splash text!' } } + +export const cacheSourceUrl = (url: string): void => { + localStorage.setItem(SPLASH_URL_KEY, url) +} + +export const hasSourceUrlChanged = (newUrl?: string): boolean => { + const cachedUrl = localStorage.getItem(SPLASH_URL_KEY) + + if ((!cachedUrl && newUrl) || (cachedUrl && !newUrl)) { + return true + } + + return cachedUrl !== newUrl +} + +export const clearSplashCache = (): void => { + localStorage.removeItem(SPLASH_CACHE_KEY) +} + +export const getCachedSplashText = (): string | null => { + return localStorage.getItem(SPLASH_CACHE_KEY) +} + +export const cacheSplashText = (text: string): void => { + localStorage.setItem(SPLASH_CACHE_KEY, text) +} + +export const getDisplayText = (splashText?: string, fallbackText?: string): string => { + const cachedText = getCachedSplashText() + + if (cachedText) return cachedText + + if (fallbackText) return fallbackText + + if (splashText && isRemoteSplashText(splashText)) return '' + + return splashText || '' +} + +export const getDirectDisplayText = (splashText?: string, fallbackText?: string): string => { + + if (splashText && !isRemoteSplashText(splashText)) return splashText + + if (fallbackText) return fallbackText + + return '' +}