mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 23:16:21 +00:00
feat(loading): redesigned loader with progress bar, task labels, configurable assets
Loading screen overhaul:
- LoadingView: Nitro V3 logo flush top-left, loading.gif at viewport
centre, large progress bar (max 900px / 90vw, h-8, gradient + glow)
anchored bottom-centre with the percentage rendered inside the bar in
Poppins, plus a friendly stage label underneath. Logo + background +
progress bar colour overridable via renderer-config keys
(loading.logo.url, loading.background, loading.progress.color).
- App.tsx: wired a real loadingProgress (0->100) + loadingTask driven by
the boot pipeline: config init (10), renderer (20), per-warmup-task
bumps for AssetManager/Localization/AvatarRender/SoundManager (25->70),
session managers (78/85/92), Communication (98), ready (100). Each bump
carries a task label looked up via a new taskLabel(key, fallback)
helper so the Italian baseline ("Sto caricando il guardaroba",
"Connessione al server", ...) can be translated by editing
renderer-config; fallback keeps current strings if the key is missing.
- AvatarEffectsView: replace raw fetch(url).json() with
loadGamedata(url) so the effectmap root manifest (JSON5 with
// comments) parses correctly and supports the core/custom/seasonal
tier merge.
- fallbackToLogin: respect login.screen.enabled=false. When login is
disabled (SSO-only deployments), init failures now route to
showSessionExpired() (home + diagnostic) instead of rendering an empty
LoginView placeholder.
- scripts/write-asset-loader.mjs: the pre-React shell rendered into
#root before the JS bundle takes over was a light-blue login skeleton
(linear gradient + two grey rectangles) producing a visible flash
before the real loader appeared. Replaced with the same
radial-gradient the LoadingView paints — the handoff is now invisible.
- renderer-config.example: document the 13 loader keys so operators can
copy & translate.
This commit is contained in:
@@ -1,22 +1,77 @@
|
||||
import { FC } from 'react';
|
||||
import { GetConfiguration } from '@nitrots/nitro-renderer';
|
||||
import { FC, useMemo } from 'react';
|
||||
import loadingGif from '@/assets/images/loading/loading.gif';
|
||||
import nitroV3Logo from '@/assets/images/notifications/nitro_v3.png';
|
||||
import { Base, Column, Text } from '../../common';
|
||||
|
||||
interface LoadingViewProps {
|
||||
isError?: boolean;
|
||||
message?: string;
|
||||
homeUrl?: string;
|
||||
progress?: number;
|
||||
currentTask?: string;
|
||||
}
|
||||
|
||||
const resolveConfigUrl = (key: string): string =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const raw = GetConfiguration().getValue<string>(key, '');
|
||||
if(!raw) return '';
|
||||
|
||||
const interpolated = GetConfiguration().interpolate(raw) || raw;
|
||||
return interpolated;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const resolveConfigString = (key: string, fallback = ''): string =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const raw = GetConfiguration().getValue<string>(key, '');
|
||||
if(!raw) return fallback;
|
||||
return raw;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
|
||||
export const LoadingView: FC<LoadingViewProps> = props =>
|
||||
{
|
||||
const { isError = false, message = '', homeUrl = '' } = props;
|
||||
const { isError = false, message = '', homeUrl = '', progress, currentTask = '' } = props;
|
||||
|
||||
const customLogoUrl = useMemo(() => resolveConfigUrl('loading.logo.url'), []);
|
||||
const customBackground = useMemo(() => resolveConfigString('loading.background', ''), []);
|
||||
const progressBarColor = useMemo(() => resolveConfigString('loading.progress.color', 'linear-gradient(90deg,#4f8cff,#2563eb)'), []);
|
||||
|
||||
const clampedProgress = typeof progress === 'number' && Number.isFinite(progress)
|
||||
? Math.max(0, Math.min(100, Math.round(progress)))
|
||||
: null;
|
||||
|
||||
const backgroundStyle = customBackground
|
||||
? { background: customBackground }
|
||||
: undefined;
|
||||
|
||||
const backgroundClassName = customBackground
|
||||
? 'fixed inset-0 z-[2147483000]'
|
||||
: 'fixed inset-0 z-[2147483000] bg-[radial-gradient(#1d1a24,#003a6b)]';
|
||||
|
||||
return (
|
||||
<Column fullHeight position="fixed" className="fixed inset-0 z-[2147483000] bg-[radial-gradient(#1d1a24,#003a6b)]">
|
||||
<Column fullHeight position="fixed" className={ backgroundClassName } style={ backgroundStyle }>
|
||||
<img
|
||||
src={ nitroV3Logo }
|
||||
alt="Nitro V3"
|
||||
draggable={ false }
|
||||
className="absolute top-5 left-0 z-2 w-37.5 h-auto select-none pointer-events-none"
|
||||
/>
|
||||
<Base fullHeight className="container h-100">
|
||||
<Column fullHeight alignItems="center" justifyContent="center">
|
||||
<Base className="absolute top-[20px] left-[20px] z-[2] w-[150px] h-[100px] bg-[url('@/assets/images/notifications/nitro_v3.png')] bg-no-repeat bg-left-top" />
|
||||
{ isError && (message && message.length) ?
|
||||
<Column alignItems="center" className="absolute bottom-[20px] left-1/2 z-[3] -translate-x-1/2 max-w-[80%]" gap={ 2 }>
|
||||
<Text fontSizeCustom={ 20 } variant="white" className="text-center [text-shadow:0px_4px_4px_rgba(0,0,0,0.25)]">
|
||||
@@ -32,15 +87,59 @@ export const LoadingView: FC<LoadingViewProps> = props =>
|
||||
}
|
||||
</Column>
|
||||
:
|
||||
<Column alignItems="center" justifyContent="center" gap={ 3 } className="z-[3]">
|
||||
<img src={ loadingGif } alt="" draggable={ false } className="block w-auto h-auto select-none pointer-events-none" />
|
||||
{ message && message.length ?
|
||||
<Text fontSizeCustom={ 20 } variant="white" className="text-center [text-shadow:0px_4px_4px_rgba(0,0,0,0.25)]">
|
||||
{ message }
|
||||
</Text>
|
||||
: null
|
||||
<>
|
||||
<Column alignItems="center" justifyContent="center" className="z-[3] absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||
<img
|
||||
src={ customLogoUrl || loadingGif }
|
||||
alt=""
|
||||
draggable={ false }
|
||||
className="block w-auto h-auto max-w-[80vw] max-h-[40vh] select-none pointer-events-none"
|
||||
/>
|
||||
{ message && message.length ?
|
||||
<Text fontSizeCustom={ 22 } variant="white" className="text-center mt-4 [text-shadow:0px_4px_4px_rgba(0,0,0,0.4)] tracking-wide">
|
||||
{ message }
|
||||
</Text>
|
||||
: null
|
||||
}
|
||||
</Column>
|
||||
{ clampedProgress !== null &&
|
||||
<Column
|
||||
alignItems="center"
|
||||
gap={ 2 }
|
||||
className="absolute bottom-[8vh] left-1/2 -translate-x-1/2 z-[4] w-[min(900px,90vw)]"
|
||||
>
|
||||
<Base
|
||||
className="relative w-full h-8 rounded-full overflow-hidden border border-white/30 shadow-[0_8px_24px_rgba(0,0,0,0.45)]"
|
||||
style={ { background: 'rgba(0,0,0,0.45)', backdropFilter: 'blur(4px)' } }
|
||||
>
|
||||
<Base
|
||||
className="h-full rounded-full transition-[width] duration-300 ease-out"
|
||||
style={ { width: `${ clampedProgress }%`, background: progressBarColor, boxShadow: '0 0 18px rgba(79,140,255,0.55)' } }
|
||||
/>
|
||||
<Base
|
||||
className="absolute inset-0 flex items-center justify-center pointer-events-none"
|
||||
style={ { fontFamily: '"Poppins","Segoe UI",system-ui,sans-serif', fontWeight: 700, fontSize: '16px', color: '#fff', letterSpacing: '0.08em', textShadow: '0 2px 4px rgba(0,0,0,0.6)' } }
|
||||
>
|
||||
{ clampedProgress }%
|
||||
</Base>
|
||||
</Base>
|
||||
<Base
|
||||
className="text-center"
|
||||
style={ {
|
||||
fontFamily: '"Poppins","Segoe UI",system-ui,sans-serif',
|
||||
fontWeight: 500,
|
||||
fontSize: '15px',
|
||||
color: 'rgba(255,255,255,0.85)',
|
||||
letterSpacing: '0.04em',
|
||||
textShadow: '0 2px 4px rgba(0,0,0,0.5)',
|
||||
minHeight: '22px'
|
||||
} }
|
||||
>
|
||||
{ currentTask }
|
||||
</Base>
|
||||
</Column>
|
||||
}
|
||||
</Column>
|
||||
</>
|
||||
}
|
||||
</Column>
|
||||
</Base>
|
||||
|
||||
Reference in New Issue
Block a user