fix: optimized splashText working process

This commit is contained in:
Maxim Grigorev 2025-05-16 22:47:59 +07:00
commit 051cc5b35c
4 changed files with 91 additions and 14 deletions

View file

@ -26,6 +26,7 @@
}
],
"rightSideText": "A Minecraft client clone in the browser!",
"splashTextFallback": "GEN is cooking",
"splashText": "https://jsonplaceholder.typicode.com/posts/1",
"pauseLinks": [
[

View file

@ -24,6 +24,7 @@ export type AppConfig = {
// hideSettings?: Record<string, boolean>
allowAutoConnect?: boolean
splashText?: string
splashTextFallback?: string
pauseLinks?: Array<Array<Record<string, any>>>
keybindings?: Record<string, any>
defaultLanguage?: string

View file

@ -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 ({
<div className={styles['game-title']}>
<div className={styles.minecraft}>
<div className={styles.edition} />
<span className={styles.splash}>{splashText}</span>
{showSplash && <span className={styles.splash}>{splashText}</span>}
</div>
</div>

View file

@ -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<string> => {
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 ''
}