mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 02:37:05 +02:00
Feature: option to turn off automatic tank model loading
Lets people on limited data stop the Tankopedia from downloading 3D models automatically. - Added an Automatically load 3D models switch to the settings page, on by default - When off, the tank page hero shows a Load 3D model button where the loading bar sits - Held back every model download on the tank page until requested, including the dynamic armor switcher and the gun flexibility visualizer - Wrapped the tank sandbox in its own suspense boundary so the loading bar shows real progress instead of the whole page waiting on the model - Measured loading progress from when each loading bar appears so it always starts empty - Added the English strings; other languages fall back until translated Fixes #311
This commit is contained in:
1 parent
116c30be88
commit
541da65bfc
9 files changed
+79
-11
No files matched your search
@@ -215,6 +215,7 @@
|
|||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
"description": "Change BlitzKit settings",
|
"description": "Change BlitzKit settings",
|
||||||
"dev_mode": "Development mode",
|
"dev_mode": "Development mode",
|
||||||
|
"auto_load_models": "Automatically load 3D models",
|
||||||
"language": "Language"
|
"language": "Language"
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -458,6 +459,8 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"sandbox": {
|
"sandbox": {
|
||||||
|
"load_model": "Load 3D model",
|
||||||
|
|
||||||
"poses": {
|
"poses": {
|
||||||
"hull_down": "Hull down",
|
"hull_down": "Hull down",
|
||||||
"face_hug": "Face hug",
|
"face_hug": "Face hug",
|
||||||
|
|||||||
+3
-1
@@ -13,6 +13,7 @@ import { modelTransformEvent } from "../../../../../../../core/blitzkit/modelTra
|
|||||||
import { Var } from "../../../../../../../core/radix/var";
|
import { Var } from "../../../../../../../core/radix/var";
|
||||||
import { useLocale } from "../../../../../../../hooks/useLocale";
|
import { useLocale } from "../../../../../../../hooks/useLocale";
|
||||||
import { Duel } from "../../../../../../../stores/duel";
|
import { Duel } from "../../../../../../../stores/duel";
|
||||||
|
import { Tankopedia } from "../../../../../../../stores/tankopedia";
|
||||||
import type { MaybeSkeletonComponentProps } from "../../../../../../../types/maybeSkeletonComponentProps";
|
import type { MaybeSkeletonComponentProps } from "../../../../../../../types/maybeSkeletonComponentProps";
|
||||||
import { VisualizerCard } from "../VisualizerCard";
|
import { VisualizerCard } from "../VisualizerCard";
|
||||||
import { VisualizerCornerStat } from "../VisualizerCornerStat";
|
import { VisualizerCornerStat } from "../VisualizerCornerStat";
|
||||||
@@ -43,6 +44,7 @@ export function GunFlexibilityVisualizer({
|
|||||||
const tank = Duel.use((state) => state.protagonist.tank);
|
const tank = Duel.use((state) => state.protagonist.tank);
|
||||||
const turret = Duel.use((state) => state.protagonist.turret);
|
const turret = Duel.use((state) => state.protagonist.turret);
|
||||||
const gun = Duel.use((state) => state.protagonist.gun);
|
const gun = Duel.use((state) => state.protagonist.gun);
|
||||||
|
const modelRequested = Tankopedia.use((state) => state.modelRequested);
|
||||||
|
|
||||||
const tankModel = modelDefinition.models[tank.id];
|
const tankModel = modelDefinition.models[tank.id];
|
||||||
const turretModel = tankModel.turrets[turret.id];
|
const turretModel = tankModel.turrets[turret.id];
|
||||||
@@ -193,7 +195,7 @@ export function GunFlexibilityVisualizer({
|
|||||||
top="0"
|
top="0"
|
||||||
left="0"
|
left="0"
|
||||||
>
|
>
|
||||||
{!skeleton && <FlexibilityCanvas />}
|
{!skeleton && modelRequested && <FlexibilityCanvas />}
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
position="absolute"
|
position="absolute"
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
const revealed = Tankopedia.use((state) => state.revealed);
|
const revealed = Tankopedia.use((state) => state.revealed);
|
||||||
const disturbed = Tankopedia.use((state) => state.disturbed);
|
const disturbed = Tankopedia.use((state) => state.disturbed);
|
||||||
const highGraphics = TankopediaPersistent.use((state) => state.highGraphics);
|
const highGraphics = TankopediaPersistent.use((state) => state.highGraphics);
|
||||||
|
const modelRequested = Tankopedia.use((state) => state.modelRequested);
|
||||||
const equalize = Duel.use((state) => state.equalize);
|
const equalize = Duel.use((state) => state.equalize);
|
||||||
const equalizer = Duel.use((state) => state.antagonist.tank.equalizer);
|
const equalizer = Duel.use((state) => state.antagonist.tank.equalizer);
|
||||||
|
|
||||||
@@ -283,7 +284,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
<SpallLinerSwitcher />
|
<SpallLinerSwitcher />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{!skeleton && (
|
{!skeleton && modelRequested && (
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<DynamicArmorSwitcher />
|
<DynamicArmorSwitcher />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|||||||
+39
-4
@@ -1,11 +1,14 @@
|
|||||||
import { Flex, Heading } from "@radix-ui/themes";
|
import { DownloadIcon } from "@radix-ui/react-icons";
|
||||||
|
import { Button, Flex, Heading } from "@radix-ui/themes";
|
||||||
import { useLocale } from "../../../../../../hooks/useLocale";
|
import { useLocale } from "../../../../../../hooks/useLocale";
|
||||||
import { Duel } from "../../../../../../stores/duel";
|
import { Duel } from "../../../../../../stores/duel";
|
||||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
||||||
|
import type { MaybeSkeletonComponentProps } from "../../../../../../types/maybeSkeletonComponentProps";
|
||||||
import { Tracker } from "./components/Tracker";
|
import { Tracker } from "./components/Tracker";
|
||||||
|
|
||||||
export function Title() {
|
export function Title({ skeleton }: MaybeSkeletonComponentProps) {
|
||||||
const { unwrap } = useLocale();
|
const { unwrap, strings } = useLocale();
|
||||||
|
const modelRequested = Tankopedia.use((state) => state.modelRequested);
|
||||||
const protagonist = Duel.use((state) => state.protagonist.tank);
|
const protagonist = Duel.use((state) => state.protagonist.tank);
|
||||||
const revealed = Tankopedia.use((state) => state.revealed);
|
const revealed = Tankopedia.use((state) => state.revealed);
|
||||||
const disturbed = Tankopedia.use((state) => state.disturbed);
|
const disturbed = Tankopedia.use((state) => state.disturbed);
|
||||||
@@ -21,6 +24,8 @@ export function Title() {
|
|||||||
onPointerDown={(event) => {
|
onPointerDown={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (!modelRequested) return;
|
||||||
|
|
||||||
Tankopedia.mutate((draft) => {
|
Tankopedia.mutate((draft) => {
|
||||||
draft.revealed = true;
|
draft.revealed = true;
|
||||||
});
|
});
|
||||||
@@ -60,7 +65,37 @@ export function Title() {
|
|||||||
{name}
|
{name}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
{!revealed && <Tracker fontSize={fontSize} />}
|
{!skeleton && !revealed && modelRequested && (
|
||||||
|
<Tracker fontSize={fontSize} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!skeleton && !modelRequested && (
|
||||||
|
<Flex
|
||||||
|
position="absolute"
|
||||||
|
left="50%"
|
||||||
|
top="50%"
|
||||||
|
width="min(100%, 16rem)"
|
||||||
|
justify="center"
|
||||||
|
style={{
|
||||||
|
transform: `translate(-50%, calc(${fontSize} * 0.75 + var(--space-4) - 50%))`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="surface"
|
||||||
|
color="gray"
|
||||||
|
highContrast
|
||||||
|
onPointerDown={(event) => event.stopPropagation()}
|
||||||
|
onClick={() => {
|
||||||
|
Tankopedia.mutate((draft) => {
|
||||||
|
draft.modelRequested = true;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DownloadIcon />
|
||||||
|
{strings.website.tools.tankopedia.sandbox.load_model}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Box, Flex } from "@radix-ui/themes";
|
import { Box, Flex } from "@radix-ui/themes";
|
||||||
import { times } from "lodash-es";
|
import { times } from "lodash-es";
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
import { Suspense, useEffect, useMemo, useRef } from "react";
|
||||||
import { api } from "../../../core/blitzkit/api";
|
import { api } from "../../../core/blitzkit/api";
|
||||||
import { defaultEqualizer } from "../../../core/blitzkit/tankToDuelMember";
|
import { defaultEqualizer } from "../../../core/blitzkit/tankToDuelMember";
|
||||||
import { useFullScreen } from "../../../hooks/useFullScreen";
|
import { useFullScreen } from "../../../hooks/useFullScreen";
|
||||||
@@ -20,6 +20,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
const canvas = useRef<HTMLCanvasElement>(null!);
|
const canvas = useRef<HTMLCanvasElement>(null!);
|
||||||
const isFullScreen = useFullScreen();
|
const isFullScreen = useFullScreen();
|
||||||
const protagonist = Duel.use((state) => state.protagonist.tank);
|
const protagonist = Duel.use((state) => state.protagonist.tank);
|
||||||
|
const modelRequested = Tankopedia.use((state) => state.modelRequested);
|
||||||
const thicknessRange = useMemo(() => {
|
const thicknessRange = useMemo(() => {
|
||||||
const entries = Object.values(tankDefinitions.tanks);
|
const entries = Object.values(tankDefinitions.tanks);
|
||||||
const filtered = entries.filter(
|
const filtered = entries.filter(
|
||||||
@@ -131,13 +132,15 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
top="0"
|
top="0"
|
||||||
left="0"
|
left="0"
|
||||||
>
|
>
|
||||||
{!skeleton && (
|
{!skeleton && modelRequested && (
|
||||||
<TankSandbox ref={canvas} thicknessRange={thicknessRange} />
|
<Suspense>
|
||||||
|
<TankSandbox ref={canvas} thicknessRange={thicknessRange} />
|
||||||
|
</Suspense>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Title />
|
<Title skeleton={skeleton} />
|
||||||
|
|
||||||
<Options
|
<Options
|
||||||
skeleton={skeleton}
|
skeleton={skeleton}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import { useRef } from "react";
|
|||||||
|
|
||||||
export function useStableProgress() {
|
export function useStableProgress() {
|
||||||
const { loaded, total } = useProgress();
|
const { loaded, total } = useProgress();
|
||||||
|
const baseline = useRef(loaded);
|
||||||
const progress = useRef(0);
|
const progress = useRef(0);
|
||||||
|
const pending = total - baseline.current;
|
||||||
|
|
||||||
progress.current = Math.max(
|
progress.current = Math.max(
|
||||||
progress.current,
|
progress.current,
|
||||||
total === 0 ? 0 : (loaded / total) * 100
|
pending <= 0 ? 0 : ((loaded - baseline.current) / pending) * 100
|
||||||
);
|
);
|
||||||
|
|
||||||
return progress.current;
|
return progress.current;
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ export function Page({ locale }: LocaleAcceptorProps) {
|
|||||||
|
|
||||||
function Content({ locale }: LocaleAcceptorProps) {
|
function Content({ locale }: LocaleAcceptorProps) {
|
||||||
const developerMode = App.useDeferred((state) => state.developerMode, false);
|
const developerMode = App.useDeferred((state) => state.developerMode, false);
|
||||||
|
const autoLoadModels = App.useDeferred(
|
||||||
|
(state) => state.autoLoadModels,
|
||||||
|
true,
|
||||||
|
);
|
||||||
const { strings } = useLocale();
|
const { strings } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -36,6 +40,19 @@ function Content({ locale }: LocaleAcceptorProps) {
|
|||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
<Flex align="center" gap="2" justify="between">
|
||||||
|
<Text>{strings.website.settings.auto_load_models}</Text>
|
||||||
|
<Switch
|
||||||
|
variant="classic"
|
||||||
|
checked={autoLoadModels}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
App.mutate((draft) => {
|
||||||
|
draft.autoLoadModels = checked;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<Flex align="center" gap="2" justify="between">
|
<Flex align="center" gap="2" justify="between">
|
||||||
<Text>{strings.website.settings.language}</Text>
|
<Text>{strings.website.settings.language}</Text>
|
||||||
<LocaleSwitcher locale={locale} />
|
<LocaleSwitcher locale={locale} />
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export interface WargamingLogin {
|
|||||||
|
|
||||||
export interface App {
|
export interface App {
|
||||||
developerMode: boolean;
|
developerMode: boolean;
|
||||||
|
autoLoadModels: boolean;
|
||||||
policiesAgreementIndex: number;
|
policiesAgreementIndex: number;
|
||||||
logins: {
|
logins: {
|
||||||
wargaming?: WargamingLogin;
|
wargaming?: WargamingLogin;
|
||||||
@@ -17,6 +18,7 @@ export interface App {
|
|||||||
export const App = new Varuna<App>(
|
export const App = new Varuna<App>(
|
||||||
{
|
{
|
||||||
developerMode: false,
|
developerMode: false,
|
||||||
|
autoLoadModels: true,
|
||||||
policiesAgreementIndex: -1,
|
policiesAgreementIndex: -1,
|
||||||
logins: {},
|
logins: {},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type { ArmorType } from "../components/Armor/components/SpacedArmorScene"
|
|||||||
import type { ExternalModuleVariant } from "../components/Armor/components/SpacedArmorSceneComponent";
|
import type { ExternalModuleVariant } from "../components/Armor/components/SpacedArmorSceneComponent";
|
||||||
import type { XP_MULTIPLIERS } from "../components/Tankopedia/TechTreeSection";
|
import type { XP_MULTIPLIERS } from "../components/Tankopedia/TechTreeSection";
|
||||||
import { api } from "../core/blitzkit/api";
|
import { api } from "../core/blitzkit/api";
|
||||||
|
import { App } from "./app";
|
||||||
import { TankopediaDisplay } from "./tankopediaPersistent/constants";
|
import { TankopediaDisplay } from "./tankopediaPersistent/constants";
|
||||||
|
|
||||||
export interface ShotLayerBase {
|
export interface ShotLayerBase {
|
||||||
@@ -79,6 +80,7 @@ export enum TankopediaRelativeAgainst {
|
|||||||
interface Tankopedia {
|
interface Tankopedia {
|
||||||
disturbed: boolean;
|
disturbed: boolean;
|
||||||
revealed: boolean;
|
revealed: boolean;
|
||||||
|
modelRequested: boolean;
|
||||||
shot?: Shot;
|
shot?: Shot;
|
||||||
skills: Record<string, number>;
|
skills: Record<string, number>;
|
||||||
relativeAgainst: TankopediaRelativeAgainst;
|
relativeAgainst: TankopediaRelativeAgainst;
|
||||||
@@ -110,6 +112,7 @@ const skillDefinitions = await api.skillDefinitions();
|
|||||||
export const Tankopedia = new Varuna<Tankopedia, ModelDefinition>((model) => ({
|
export const Tankopedia = new Varuna<Tankopedia, ModelDefinition>((model) => ({
|
||||||
disturbed: false,
|
disturbed: false,
|
||||||
revealed: false,
|
revealed: false,
|
||||||
|
modelRequested: App.state.autoLoadModels,
|
||||||
relativeAgainst: TankopediaRelativeAgainst.Class,
|
relativeAgainst: TankopediaRelativeAgainst.Class,
|
||||||
editStatic: false,
|
editStatic: false,
|
||||||
skills: createDefaultSkills(skillDefinitions),
|
skills: createDefaultSkills(skillDefinitions),
|
||||||
|
|||||||
Reference in new issue
Block a user