mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: simplified duel state
This commit is contained in:
1 parent
7314f1d9b1
commit
66016dedf0
13 files changed
+276
-246
No files matched your search
@@ -59,6 +59,7 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-fuzzysort": "^1.0.1",
|
"react-fuzzysort": "^1.0.1",
|
||||||
|
"react-promise-suspense": "^0.3.4",
|
||||||
"react-spinners": "^0.13.8",
|
"react-spinners": "^0.13.8",
|
||||||
"satori": "^0.10.8",
|
"satori": "^0.10.8",
|
||||||
"sharp": "^0.33.2",
|
"sharp": "^0.33.2",
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@radix-ui/themes';
|
} from '@radix-ui/themes';
|
||||||
import { go } from 'fuzzysort';
|
import { go } from 'fuzzysort';
|
||||||
|
import { produce } from 'immer';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { use, useRef, useState } from 'react';
|
import { Dispatch, SetStateAction, use, useRef, useState } from 'react';
|
||||||
import { ModuleButton } from '../../../../../components/ModuleButton';
|
import { ModuleButton } from '../../../../../components/ModuleButton';
|
||||||
import { SmallTankIcon } from '../../../../../components/SmallTankIcon';
|
import { SmallTankIcon } from '../../../../../components/SmallTankIcon';
|
||||||
import {
|
import {
|
||||||
@@ -25,23 +26,26 @@ import {
|
|||||||
tankDefinitions,
|
tankDefinitions,
|
||||||
tankNamesDiacritics,
|
tankNamesDiacritics,
|
||||||
} from '../../../../../core/blitzkrieg/tankDefinitions';
|
} from '../../../../../core/blitzkrieg/tankDefinitions';
|
||||||
import mutateTankopedia, {
|
import { useTankopedia } from '../../../../../stores/tankopedia';
|
||||||
useTankopedia,
|
import { Duel } from '../page';
|
||||||
} from '../../../../../stores/tankopedia';
|
|
||||||
|
|
||||||
export function AntagonistBar() {
|
interface AntagonistBarProps {
|
||||||
|
duel: Duel;
|
||||||
|
setDuel: Dispatch<SetStateAction<Duel>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AntagonistBar({
|
||||||
|
duel: { antagonist },
|
||||||
|
setDuel,
|
||||||
|
}: AntagonistBarProps) {
|
||||||
const awaitedTankDefinitions = use(tankDefinitions);
|
const awaitedTankDefinitions = use(tankDefinitions);
|
||||||
const mode = useTankopedia((state) => state.mode);
|
const mode = useTankopedia((state) => state.mode);
|
||||||
const awaitedTankNamesDiacritics = use(tankNamesDiacritics);
|
const awaitedTankNamesDiacritics = use(tankNamesDiacritics);
|
||||||
const [tab, setTab] = useState('search');
|
const [tab, setTab] = useState('search');
|
||||||
const searchInput = useRef<HTMLInputElement>(null);
|
const searchInput = useRef<HTMLInputElement>(null);
|
||||||
const antagonist = useTankopedia((state) => {
|
|
||||||
if (!state.areTanksAssigned) return;
|
|
||||||
return state.antagonist;
|
|
||||||
});
|
|
||||||
const [searchResults, setSearchResults] = useState<number[]>([]);
|
const [searchResults, setSearchResults] = useState<number[]>([]);
|
||||||
|
|
||||||
if (!antagonist || mode !== 'armor') return null;
|
if (mode !== 'armor') return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
@@ -58,10 +62,11 @@ export function AntagonistBar() {
|
|||||||
key={shell.id}
|
key={shell.id}
|
||||||
last={index === antagonist.gun.shells.length - 1}
|
last={index === antagonist.gun.shells.length - 1}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTankopedia((draft) => {
|
setDuel(
|
||||||
if (!draft.areTanksAssigned) return;
|
produce<Duel>((draft) => {
|
||||||
draft.antagonist.shell = shell;
|
draft.antagonist.shell = shell;
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -147,17 +152,17 @@ export function AntagonistBar() {
|
|||||||
key={tank.id}
|
key={tank.id}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTankopedia((draft) => {
|
setDuel(
|
||||||
if (!draft.areTanksAssigned) return;
|
produce<Duel>((draft) => {
|
||||||
|
draft.antagonist.tank = tank;
|
||||||
draft.antagonist.tank = tank;
|
draft.antagonist.turret =
|
||||||
draft.antagonist.turret =
|
tank.turrets.at(-1)!;
|
||||||
tank.turrets.at(-1)!;
|
draft.antagonist.gun =
|
||||||
draft.antagonist.gun =
|
draft.antagonist.turret.guns.at(-1)!;
|
||||||
draft.antagonist.turret.guns.at(-1)!;
|
draft.antagonist.shell =
|
||||||
draft.antagonist.shell =
|
draft.antagonist.gun.shells[0];
|
||||||
draft.antagonist.gun.shells[0];
|
}),
|
||||||
});
|
);
|
||||||
setSearchResults([]);
|
setSearchResults([]);
|
||||||
searchInput.current!.value = '';
|
searchInput.current!.value = '';
|
||||||
setTab('configure');
|
setTab('configure');
|
||||||
@@ -200,15 +205,15 @@ export function AntagonistBar() {
|
|||||||
}
|
}
|
||||||
key={turret.id}
|
key={turret.id}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTankopedia((draft) => {
|
setDuel(
|
||||||
if (!draft.areTanksAssigned) return;
|
produce<Duel>((draft) => {
|
||||||
|
draft.antagonist.turret = turret;
|
||||||
draft.antagonist.turret = turret;
|
draft.antagonist.gun =
|
||||||
draft.antagonist.gun =
|
turret.guns.at(-1)!;
|
||||||
turret.guns.at(-1)!;
|
draft.antagonist.shell =
|
||||||
draft.antagonist.shell =
|
draft.antagonist.gun.shells[0];
|
||||||
draft.antagonist.gun.shells[0];
|
}),
|
||||||
});
|
);
|
||||||
}}
|
}}
|
||||||
selected={antagonist.turret.id === turret.id}
|
selected={antagonist.turret.id === turret.id}
|
||||||
tier={turret.tier}
|
tier={turret.tier}
|
||||||
@@ -228,11 +233,12 @@ export function AntagonistBar() {
|
|||||||
index === antagonist.turret.guns.length - 1
|
index === antagonist.turret.guns.length - 1
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTankopedia((draft) => {
|
setDuel(
|
||||||
if (!draft.areTanksAssigned) return;
|
produce<Duel>((draft) => {
|
||||||
draft.antagonist.gun = gun;
|
draft.antagonist.gun = gun;
|
||||||
draft.antagonist.shell = gun.shells[0];
|
draft.antagonist.shell = gun.shells[0];
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
selected={antagonist.gun.id === gun.id}
|
selected={antagonist.gun.id === gun.id}
|
||||||
tier={gun.tier}
|
tier={gun.tier}
|
||||||
@@ -277,14 +283,14 @@ export function AntagonistBar() {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTankopedia((draft) => {
|
setDuel(
|
||||||
if (!draft.areTanksAssigned) return;
|
produce<Duel>((draft) => {
|
||||||
|
[draft.protagonist, draft.antagonist] = [
|
||||||
[draft.antagonist, draft.protagonist] = [
|
draft.antagonist,
|
||||||
draft.protagonist,
|
draft.protagonist,
|
||||||
draft.antagonist,
|
];
|
||||||
];
|
}),
|
||||||
});
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ShuffleIcon /> Swap tanks
|
<ShuffleIcon /> Swap tanks
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import mutateTankopedia, {
|
|||||||
useTankopedia,
|
useTankopedia,
|
||||||
} from '../../../../../../stores/tankopedia';
|
} from '../../../../../../stores/tankopedia';
|
||||||
import { Loader } from '../../../../components/Loader';
|
import { Loader } from '../../../../components/Loader';
|
||||||
|
import { Duel } from '../../page';
|
||||||
import { Controls } from '../Control';
|
import { Controls } from '../Control';
|
||||||
import { Lighting } from '../Lighting';
|
import { Lighting } from '../Lighting';
|
||||||
import { RotationInputs } from '../RotationInputs';
|
import { RotationInputs } from '../RotationInputs';
|
||||||
@@ -18,16 +19,16 @@ import { Options } from './components/Options';
|
|||||||
import { TankArmor } from './components/TankArmor';
|
import { TankArmor } from './components/TankArmor';
|
||||||
import { TankModel } from './components/TankModel';
|
import { TankModel } from './components/TankModel';
|
||||||
|
|
||||||
export function TankDisplay() {
|
interface TankDisplayProps {
|
||||||
|
duel: Duel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TankDisplay({ duel }: TankDisplayProps) {
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
const awaitedModelDefinitions = use(modelDefinitions);
|
const awaitedModelDefinitions = use(modelDefinitions);
|
||||||
const canvasWrapper = useRef<HTMLDivElement>(null);
|
const canvasWrapper = useRef<HTMLDivElement>(null);
|
||||||
const [isFullScreen, setIsFullScreen] = useState(false);
|
const [isFullScreen, setIsFullScreen] = useState(false);
|
||||||
const mode = useTankopedia((state) => state.mode);
|
const mode = useTankopedia((state) => state.mode);
|
||||||
const protagonist = useTankopedia((state) => {
|
|
||||||
if (!state.areTanksAssigned) return;
|
|
||||||
return state.protagonist;
|
|
||||||
});
|
|
||||||
|
|
||||||
function handlePointerDown() {
|
function handlePointerDown() {
|
||||||
window.addEventListener('pointermove', handlePointerMove);
|
window.addEventListener('pointermove', handlePointerMove);
|
||||||
@@ -56,12 +57,14 @@ export function TankDisplay() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!protagonist) return void 0;
|
if (!duel.protagonist) return void 0;
|
||||||
|
|
||||||
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
|
const tankModelDefinition =
|
||||||
|
awaitedModelDefinitions[duel.protagonist.tank.id];
|
||||||
const turretModelDefinition =
|
const turretModelDefinition =
|
||||||
tankModelDefinition.turrets[protagonist.turret.id];
|
tankModelDefinition.turrets[duel.protagonist.turret.id];
|
||||||
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
const gunModelDefinition =
|
||||||
|
turretModelDefinition.guns[duel.protagonist.gun.id];
|
||||||
|
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
[draft.model.physical.pitch, draft.model.physical.yaw] =
|
[draft.model.physical.pitch, draft.model.physical.yaw] =
|
||||||
@@ -72,9 +75,7 @@ export function TankDisplay() {
|
|||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}, [protagonist?.gun, protagonist?.turret]);
|
}, [duel.protagonist.gun, duel.protagonist.turret]);
|
||||||
|
|
||||||
if (!protagonist) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Theme radius={isFullScreen ? 'none' : undefined}>
|
<Theme radius={isFullScreen ? 'none' : undefined}>
|
||||||
@@ -130,15 +131,15 @@ export function TankDisplay() {
|
|||||||
</Html>
|
</Html>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<TankModel />
|
<TankModel duel={duel} />
|
||||||
<TankArmor />
|
<TankArmor duel={duel} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Options canvas={canvasWrapper} isFullScreen={isFullScreen} />
|
<Options canvas={canvasWrapper} isFullScreen={isFullScreen} />
|
||||||
|
|
||||||
<RotationInputs />
|
<RotationInputs duel={duel} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Theme>
|
</Theme>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useLoader } from '@react-three/fiber';
|
import { useLoader } from '@react-three/fiber';
|
||||||
import { memo, useEffect, useRef, useState } from 'react';
|
import { memo, useEffect, useRef } from 'react';
|
||||||
import { Euler, Group, Mesh, Vector3 } from 'three';
|
import { Euler, Group, Mesh, Vector3 } from 'three';
|
||||||
import { GLTFLoader } from 'three-stdlib';
|
import { GLTFLoader } from 'three-stdlib';
|
||||||
import { degToRad } from 'three/src/math/MathUtils';
|
import { degToRad } from 'three/src/math/MathUtils';
|
||||||
@@ -7,27 +7,23 @@ import { ArmorMesh } from '../../../../../../../components/ArmorMesh';
|
|||||||
import { HeadsUpDisplay } from '../../../../../../../components/HeadsUpDisplay';
|
import { HeadsUpDisplay } from '../../../../../../../components/HeadsUpDisplay';
|
||||||
import { X_AXIS, Y_AXIS, Z_AXIS } from '../../../../../../../constants/axis';
|
import { X_AXIS, Y_AXIS, Z_AXIS } from '../../../../../../../constants/axis';
|
||||||
import { asset } from '../../../../../../../core/blitzkrieg/asset';
|
import { asset } from '../../../../../../../core/blitzkrieg/asset';
|
||||||
import {
|
|
||||||
ModelDefinitions,
|
|
||||||
modelDefinitions,
|
|
||||||
} from '../../../../../../../core/blitzkrieg/modelDefinitions';
|
|
||||||
import {
|
import {
|
||||||
ModelTransformEventData,
|
ModelTransformEventData,
|
||||||
modelTransformEvent,
|
modelTransformEvent,
|
||||||
} from '../../../../../../../core/blitzkrieg/modelTransform';
|
} from '../../../../../../../core/blitzkrieg/modelTransform';
|
||||||
import { nameToArmorId } from '../../../../../../../core/blitzkrieg/nameToArmorId';
|
import { nameToArmorId } from '../../../../../../../core/blitzkrieg/nameToArmorId';
|
||||||
import { resolveArmor } from '../../../../../../../core/blitzkrieg/resolveThickness';
|
import { resolveArmor } from '../../../../../../../core/blitzkrieg/resolveThickness';
|
||||||
|
import { useModelDefinitions } from '../../../../../../../core/hooks/useModelDefinitions';
|
||||||
import { useTankopedia } from '../../../../../../../stores/tankopedia';
|
import { useTankopedia } from '../../../../../../../stores/tankopedia';
|
||||||
|
import { Duel } from '../../../page';
|
||||||
|
|
||||||
export const TankArmor = memo(() => {
|
interface TankArmorProps {
|
||||||
|
duel: Duel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TankArmor = memo<TankArmorProps>(({ duel }) => {
|
||||||
const wrapper = useRef<Group>(null);
|
const wrapper = useRef<Group>(null);
|
||||||
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
const awaitedModelDefinitions = useModelDefinitions();
|
||||||
ModelDefinitions | undefined
|
|
||||||
>(undefined);
|
|
||||||
const protagonist = useTankopedia((state) => {
|
|
||||||
if (!state.areTanksAssigned) return;
|
|
||||||
return state.protagonist;
|
|
||||||
});
|
|
||||||
const showSpacedArmor = useTankopedia(
|
const showSpacedArmor = useTankopedia(
|
||||||
(state) => state.model.visual.showSpacedArmor,
|
(state) => state.model.visual.showSpacedArmor,
|
||||||
);
|
);
|
||||||
@@ -35,12 +31,6 @@ export const TankArmor = memo(() => {
|
|||||||
const gunContainer = useRef<Group>(null);
|
const gunContainer = useRef<Group>(null);
|
||||||
const initialTankopediaState = useTankopedia.getState();
|
const initialTankopediaState = useTankopedia.getState();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
setAwaitedModelDefinitions(await modelDefinitions);
|
|
||||||
})();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!awaitedModelDefinitions) return;
|
if (!awaitedModelDefinitions) return;
|
||||||
|
|
||||||
@@ -118,25 +108,22 @@ export const TankArmor = memo(() => {
|
|||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!protagonist) return null;
|
|
||||||
|
|
||||||
const armorGltf = useLoader(
|
const armorGltf = useLoader(
|
||||||
GLTFLoader,
|
GLTFLoader,
|
||||||
asset(`3d/tanks/armor/${protagonist.tank.id}.glb`),
|
asset(`3d/tanks/armor/${duel.protagonist.tank.id}.glb`),
|
||||||
);
|
);
|
||||||
const modelGltf = useLoader(
|
const modelGltf = useLoader(
|
||||||
GLTFLoader,
|
GLTFLoader,
|
||||||
asset(`3d/tanks/models/${protagonist.tank.id}.glb`),
|
asset(`3d/tanks/models/${duel.protagonist.tank.id}.glb`),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!awaitedModelDefinitions) return null;
|
|
||||||
|
|
||||||
const armorNodes = Object.values(armorGltf.nodes);
|
const armorNodes = Object.values(armorGltf.nodes);
|
||||||
const modelNodes = Object.values(modelGltf.nodes);
|
const modelNodes = Object.values(modelGltf.nodes);
|
||||||
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
|
const tankModelDefinition = awaitedModelDefinitions[duel.protagonist.tank.id];
|
||||||
const turretModelDefinition =
|
const turretModelDefinition =
|
||||||
tankModelDefinition.turrets[protagonist.turret.id];
|
tankModelDefinition.turrets[duel.protagonist.turret.id];
|
||||||
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
const gunModelDefinition =
|
||||||
|
turretModelDefinition.guns[duel.protagonist.gun.id];
|
||||||
const turretOrigin = new Vector3(
|
const turretOrigin = new Vector3(
|
||||||
tankModelDefinition.turretOrigin[0],
|
tankModelDefinition.turretOrigin[0],
|
||||||
tankModelDefinition.turretOrigin[1],
|
tankModelDefinition.turretOrigin[1],
|
||||||
@@ -173,6 +160,7 @@ export const TankArmor = memo(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ArmorMesh
|
<ArmorMesh
|
||||||
|
duel={duel}
|
||||||
key={node.uuid}
|
key={node.uuid}
|
||||||
geometry={(node as Mesh).geometry}
|
geometry={(node as Mesh).geometry}
|
||||||
isSpaced={spaced ?? false}
|
isSpaced={spaced ?? false}
|
||||||
@@ -191,6 +179,7 @@ export const TankArmor = memo(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ArmorMesh
|
<ArmorMesh
|
||||||
|
duel={duel}
|
||||||
key={node.uuid}
|
key={node.uuid}
|
||||||
geometry={(node as Mesh).geometry}
|
geometry={(node as Mesh).geometry}
|
||||||
isSpaced
|
isSpaced
|
||||||
@@ -223,6 +212,7 @@ export const TankArmor = memo(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ArmorMesh
|
<ArmorMesh
|
||||||
|
duel={duel}
|
||||||
key={node.uuid}
|
key={node.uuid}
|
||||||
geometry={(node as Mesh).geometry}
|
geometry={(node as Mesh).geometry}
|
||||||
position={turretOrigin}
|
position={turretOrigin}
|
||||||
@@ -252,6 +242,7 @@ export const TankArmor = memo(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ArmorMesh
|
<ArmorMesh
|
||||||
|
duel={duel}
|
||||||
key={node.uuid}
|
key={node.uuid}
|
||||||
geometry={(node as Mesh).geometry}
|
geometry={(node as Mesh).geometry}
|
||||||
position={turretOrigin.clone().add(gunOrigin)}
|
position={turretOrigin.clone().add(gunOrigin)}
|
||||||
@@ -272,6 +263,7 @@ export const TankArmor = memo(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ArmorMesh
|
<ArmorMesh
|
||||||
|
duel={duel}
|
||||||
key={node.uuid}
|
key={node.uuid}
|
||||||
geometry={(node as Mesh).geometry}
|
geometry={(node as Mesh).geometry}
|
||||||
isSpaced
|
isSpaced
|
||||||
|
|||||||
@@ -26,17 +26,16 @@ import { normalizeAnglePI } from '../../../../../../../core/math/normalizeAngle1
|
|||||||
import mutateTankopedia, {
|
import mutateTankopedia, {
|
||||||
useTankopedia,
|
useTankopedia,
|
||||||
} from '../../../../../../../stores/tankopedia';
|
} from '../../../../../../../stores/tankopedia';
|
||||||
|
import { Duel } from '../../../page';
|
||||||
|
|
||||||
export const TankModel = memo(() => {
|
interface TankModelProps {
|
||||||
|
duel: Duel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => {
|
||||||
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
||||||
ModelDefinitions | undefined
|
ModelDefinitions | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
const protagonist = useTankopedia((state) => {
|
|
||||||
if (!state.areTanksAssigned) return;
|
|
||||||
return state.protagonist;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!protagonist) return null;
|
|
||||||
|
|
||||||
const canvas = useThree((state) => state.gl.domElement);
|
const canvas = useThree((state) => state.gl.domElement);
|
||||||
const physical = useTankopedia((state) => state.model.physical);
|
const physical = useTankopedia((state) => state.model.physical);
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { Flex } from '@radix-ui/themes';
|
||||||
|
import { produce } from 'immer';
|
||||||
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
|
import { ModuleButton } from '../../../../../components/ModuleButton';
|
||||||
|
import { Duel } from '../page';
|
||||||
|
|
||||||
|
interface ModulesProps {
|
||||||
|
duel: Duel;
|
||||||
|
setDuel: Dispatch<SetStateAction<Duel>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Modules({ duel, setDuel }: ModulesProps) {
|
||||||
|
return (
|
||||||
|
<Flex gap="2">
|
||||||
|
<Flex>
|
||||||
|
{duel.protagonist.tank.turrets.map((thisTurret, index) => {
|
||||||
|
return (
|
||||||
|
<ModuleButton
|
||||||
|
key={thisTurret.id}
|
||||||
|
selected={duel.protagonist.turret.id === thisTurret.id}
|
||||||
|
type="module"
|
||||||
|
module="turret"
|
||||||
|
tier={thisTurret.tier}
|
||||||
|
first={index === 0}
|
||||||
|
last={index === duel.protagonist.tank.turrets.length - 1}
|
||||||
|
rowChild
|
||||||
|
onClick={() => {
|
||||||
|
setDuel(
|
||||||
|
produce<Duel>((draft) => {
|
||||||
|
draft.protagonist.turret = thisTurret;
|
||||||
|
draft.protagonist.gun = thisTurret.guns.at(-1)!;
|
||||||
|
draft.protagonist.shell = thisTurret.guns.at(-1)!.shells[0];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex>
|
||||||
|
{duel.protagonist.turret.guns.map((thisGun, index) => {
|
||||||
|
return (
|
||||||
|
<ModuleButton
|
||||||
|
key={thisGun.id}
|
||||||
|
type="module"
|
||||||
|
module="gun"
|
||||||
|
tier={thisGun.tier}
|
||||||
|
selected={duel.protagonist.gun.id === thisGun.id}
|
||||||
|
first={index === 0}
|
||||||
|
last={index === duel.protagonist.turret.guns.length - 1}
|
||||||
|
rowChild
|
||||||
|
onClick={() => {
|
||||||
|
setDuel(
|
||||||
|
produce<Duel>((draft) => {
|
||||||
|
draft.protagonist.gun = thisGun;
|
||||||
|
draft.protagonist.shell = thisGun.shells[0];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6,16 +6,17 @@ import { modelTransformEvent } from '../../../../../core/blitzkrieg/modelTransfo
|
|||||||
import mutateTankopedia, {
|
import mutateTankopedia, {
|
||||||
useTankopedia,
|
useTankopedia,
|
||||||
} from '../../../../../stores/tankopedia';
|
} from '../../../../../stores/tankopedia';
|
||||||
|
import { Duel } from '../page';
|
||||||
|
|
||||||
export function RotationInputs() {
|
interface RotationInputsProps {
|
||||||
|
duel: Duel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RotationInputs({ duel: { protagonist } }: RotationInputsProps) {
|
||||||
const physical = useTankopedia((state) => state.model.physical);
|
const physical = useTankopedia((state) => state.model.physical);
|
||||||
const awaitedModelDefinitions = use(modelDefinitions);
|
const awaitedModelDefinitions = use(modelDefinitions);
|
||||||
const turretYawInput = useRef<HTMLInputElement>(null);
|
const turretYawInput = useRef<HTMLInputElement>(null);
|
||||||
const gunPitchInput = useRef<HTMLInputElement>(null);
|
const gunPitchInput = useRef<HTMLInputElement>(null);
|
||||||
const protagonist = useTankopedia((state) => {
|
|
||||||
if (!state.areTanksAssigned) return;
|
|
||||||
return state.protagonist;
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
turretYawInput.current!.value = `${-Math.round(
|
turretYawInput.current!.value = `${-Math.round(
|
||||||
@@ -28,8 +29,6 @@ export function RotationInputs() {
|
|||||||
)}`;
|
)}`;
|
||||||
}, [physical.pitch]);
|
}, [physical.pitch]);
|
||||||
|
|
||||||
if (!protagonist) return null;
|
|
||||||
|
|
||||||
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
|
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
|
||||||
const turretModelDefinition =
|
const turretModelDefinition =
|
||||||
tankModelDefinition.turrets[protagonist.turret.id];
|
tankModelDefinition.turrets[protagonist.turret.id];
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { ArrowLeftIcon } from '@radix-ui/react-icons';
|
||||||
|
import { Flex, Heading, Link, Text } from '@radix-ui/themes';
|
||||||
|
import { Flag } from '../../../../../components/Flag';
|
||||||
|
import { Duel } from '../page';
|
||||||
|
|
||||||
|
interface TitleProps {
|
||||||
|
duel: Duel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Title({ duel }: TitleProps) {
|
||||||
|
return (
|
||||||
|
<Flex justify="between" align="center">
|
||||||
|
<Link
|
||||||
|
href="/tools/tankopedia"
|
||||||
|
style={{ color: 'unset', textDecoration: 'none' }}
|
||||||
|
>
|
||||||
|
<Flex gap="1" align="center">
|
||||||
|
<ArrowLeftIcon />
|
||||||
|
<Text>Back</Text>
|
||||||
|
</Flex>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Flex gap="2" align="center">
|
||||||
|
<Flag nation={duel.protagonist.tank.nation} />
|
||||||
|
<Heading>{duel.protagonist.tank.name}</Heading>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,154 +1,87 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ArrowLeftIcon } from '@radix-ui/react-icons';
|
import { Flex } from '@radix-ui/themes';
|
||||||
import { Flex, Heading, Text } from '@radix-ui/themes';
|
import { produce } from 'immer';
|
||||||
import Link from 'next/link';
|
import { use, useEffect, useState } from 'react';
|
||||||
import { use, useEffect } from 'react';
|
|
||||||
import { Flag } from '../../../../components/Flag';
|
|
||||||
import { ModuleButton } from '../../../../components/ModuleButton';
|
|
||||||
import PageWrapper from '../../../../components/PageWrapper';
|
import PageWrapper from '../../../../components/PageWrapper';
|
||||||
import { tankDefinitions } from '../../../../core/blitzkrieg/tankDefinitions';
|
import { tankDefinitions } from '../../../../core/blitzkrieg/tankDefinitions';
|
||||||
import mutateTankopedia, { useTankopedia } from '../../../../stores/tankopedia';
|
import mutateTankopedia, { DuelMember } from '../../../../stores/tankopedia';
|
||||||
import { AntagonistBar } from './components/AntagonistBar';
|
import { AntagonistBar } from './components/AntagonistBar';
|
||||||
import { TankDisplay } from './components/Model/TankDisplay';
|
import { TankDisplay } from './components/Model/TankDisplay';
|
||||||
|
import { Modules } from './components/Modules';
|
||||||
|
import { Title } from './components/Title';
|
||||||
|
|
||||||
|
export interface Duel {
|
||||||
|
protagonist: DuelMember;
|
||||||
|
antagonist: DuelMember;
|
||||||
|
}
|
||||||
|
|
||||||
export default function Page({ params }: { params: { id: string } }) {
|
export default function Page({ params }: { params: { id: string } }) {
|
||||||
const id = parseInt(params.id);
|
const id = parseInt(params.id);
|
||||||
const awaitedTankDefinitions = use(tankDefinitions);
|
const awaitedTankDefinitions = use(tankDefinitions);
|
||||||
|
const [duel, setDuel] = useState<Duel>({
|
||||||
|
protagonist: {
|
||||||
|
tank: awaitedTankDefinitions[id],
|
||||||
|
turret: awaitedTankDefinitions[id].turrets.at(-1)!,
|
||||||
|
gun: awaitedTankDefinitions[id].turrets.at(-1)!.guns.at(-1)!,
|
||||||
|
shell: awaitedTankDefinitions[id].turrets.at(-1)!.guns.at(-1)!.shells[0],
|
||||||
|
},
|
||||||
|
antagonist: {
|
||||||
|
tank: awaitedTankDefinitions[id],
|
||||||
|
turret: awaitedTankDefinitions[id].turrets.at(-1)!,
|
||||||
|
gun: awaitedTankDefinitions[id].turrets.at(-1)!.guns.at(-1)!,
|
||||||
|
shell: awaitedTankDefinitions[id].turrets.at(-1)!.guns.at(-1)!.shells[0],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
draft.model.physical.yaw = 0;
|
draft.model.physical.yaw = 0;
|
||||||
draft.model.physical.pitch = 0;
|
draft.model.physical.pitch = 0;
|
||||||
draft.mode = 'model';
|
draft.mode = 'model';
|
||||||
draft.areTanksAssigned = true;
|
|
||||||
|
|
||||||
if (!draft.areTanksAssigned) return;
|
|
||||||
|
|
||||||
draft.protagonist = {} as typeof draft.protagonist;
|
|
||||||
draft.protagonist.tank = awaitedTankDefinitions[id];
|
|
||||||
draft.protagonist.turret = draft.protagonist.tank.turrets.at(-1)!;
|
|
||||||
draft.protagonist.gun = draft.protagonist.turret.guns.at(-1)!;
|
|
||||||
draft.protagonist.shell = draft.protagonist.gun.shells[0];
|
|
||||||
draft.antagonist = draft.protagonist;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleKeyDown(event: KeyboardEvent) {
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
if (event.key === '1') {
|
if (event.key === '1') {
|
||||||
mutateTankopedia((draft) => {
|
setDuel(
|
||||||
if (!draft.areTanksAssigned) return;
|
produce<Duel>((draft) => {
|
||||||
draft.antagonist.shell = draft.antagonist.gun.shells[0];
|
draft.antagonist.shell = draft.antagonist.gun.shells[0];
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
} else if (event.key === '2') {
|
} else if (event.key === '2') {
|
||||||
mutateTankopedia((draft) => {
|
setDuel(
|
||||||
if (!draft.areTanksAssigned) return;
|
produce<Duel>((draft) => {
|
||||||
if (draft.antagonist.gun.shells[1])
|
if (draft.antagonist.gun.shells[1]) {
|
||||||
draft.antagonist.shell = draft.antagonist.gun.shells[1];
|
draft.antagonist.shell = draft.antagonist.gun.shells[1];
|
||||||
});
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
} else if (event.key === '3') {
|
} else if (event.key === '3') {
|
||||||
mutateTankopedia((draft) => {
|
setDuel(
|
||||||
if (!draft.areTanksAssigned) return;
|
produce<Duel>((draft) => {
|
||||||
if (draft.antagonist.gun.shells[2])
|
if (draft.antagonist.gun.shells[2]) {
|
||||||
draft.antagonist.shell = draft.antagonist.gun.shells[2];
|
draft.antagonist.shell = draft.antagonist.gun.shells[2];
|
||||||
});
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
mutateTankopedia((draft) => {
|
|
||||||
draft.areTanksAssigned = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
window.removeEventListener('keydown', handleKeyDown);
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
};
|
};
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
const protagonist = useTankopedia((state) => {
|
|
||||||
if (!state.areTanksAssigned) return;
|
|
||||||
return state.protagonist;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!protagonist) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageWrapper color="purple">
|
<PageWrapper color="purple">
|
||||||
<Flex gap="8" direction="column">
|
<Flex gap="8" direction="column">
|
||||||
<Flex gap="4" direction="column">
|
<Flex gap="4" direction="column">
|
||||||
<Flex justify="between" align="center">
|
<Title duel={duel} />
|
||||||
<Link
|
<TankDisplay duel={duel} />
|
||||||
href="/tools/tankopedia"
|
<AntagonistBar duel={duel} setDuel={setDuel} />
|
||||||
style={{ color: 'unset', textDecoration: 'none' }}
|
<Modules duel={duel} setDuel={setDuel} />
|
||||||
>
|
|
||||||
<Flex gap="1" align="center">
|
|
||||||
<ArrowLeftIcon />
|
|
||||||
<Text>Back</Text>
|
|
||||||
</Flex>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Flex gap="2" align="center">
|
|
||||||
<Flag nation={protagonist.tank.nation} />
|
|
||||||
<Heading>{protagonist.tank.name}</Heading>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<TankDisplay />
|
|
||||||
<AntagonistBar />
|
|
||||||
|
|
||||||
<Flex gap="2">
|
|
||||||
<Flex>
|
|
||||||
{protagonist.tank.turrets.map((thisTurret, index) => {
|
|
||||||
return (
|
|
||||||
<ModuleButton
|
|
||||||
key={thisTurret.id}
|
|
||||||
selected={protagonist.turret.id === thisTurret.id}
|
|
||||||
type="module"
|
|
||||||
module="turret"
|
|
||||||
tier={thisTurret.tier}
|
|
||||||
first={index === 0}
|
|
||||||
last={index === protagonist.tank.turrets.length - 1}
|
|
||||||
rowChild
|
|
||||||
onClick={() => {
|
|
||||||
mutateTankopedia((draft) => {
|
|
||||||
if (!draft.areTanksAssigned) return;
|
|
||||||
|
|
||||||
draft.protagonist.turret = thisTurret;
|
|
||||||
draft.protagonist.gun = thisTurret.guns.at(-1)!;
|
|
||||||
draft.protagonist.shell =
|
|
||||||
draft.protagonist.gun.shells[0];
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex>
|
|
||||||
{protagonist.turret.guns.map((thisGun, index) => {
|
|
||||||
return (
|
|
||||||
<ModuleButton
|
|
||||||
key={thisGun.id}
|
|
||||||
type="module"
|
|
||||||
module="gun"
|
|
||||||
tier={thisGun.tier}
|
|
||||||
selected={protagonist.gun.id === thisGun.id}
|
|
||||||
first={index === 0}
|
|
||||||
last={index === protagonist.turret.guns.length - 1}
|
|
||||||
rowChild
|
|
||||||
onClick={() => {
|
|
||||||
mutateTankopedia((draft) => {
|
|
||||||
if (!draft.areTanksAssigned) return;
|
|
||||||
|
|
||||||
draft.protagonist.gun = thisGun;
|
|
||||||
draft.protagonist.shell = thisGun.shells[0];
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ import { MeshProps } from '@react-three/fiber';
|
|||||||
import { MeshStandardMaterial } from 'three';
|
import { MeshStandardMaterial } from 'three';
|
||||||
import CustomShaderMaterial from 'three-custom-shader-material';
|
import CustomShaderMaterial from 'three-custom-shader-material';
|
||||||
import { degToRad } from 'three/src/math/MathUtils';
|
import { degToRad } from 'three/src/math/MathUtils';
|
||||||
|
import { Duel } from '../../app/tools/tankopedia/[id]/page';
|
||||||
import { canSplash } from '../../core/blitz/canSplash';
|
import { canSplash } from '../../core/blitz/canSplash';
|
||||||
import { isExplosive } from '../../core/blitz/isExplosive';
|
import { isExplosive } from '../../core/blitz/isExplosive';
|
||||||
import { resolveNearPenetration } from '../../core/blitz/resolveNearPenetration';
|
import { resolveNearPenetration } from '../../core/blitz/resolveNearPenetration';
|
||||||
import { useTankopedia } from '../../stores/tankopedia';
|
|
||||||
import fragmentShader from './shaders/fragment.glsl';
|
import fragmentShader from './shaders/fragment.glsl';
|
||||||
import vertexShader from './shaders/vertex.glsl';
|
import vertexShader from './shaders/vertex.glsl';
|
||||||
|
|
||||||
interface ArmorMeshProps extends MeshProps {
|
interface ArmorMeshProps extends MeshProps {
|
||||||
|
duel: Duel;
|
||||||
isSpaced: boolean;
|
isSpaced: boolean;
|
||||||
isExternalModule?: boolean;
|
isExternalModule?: boolean;
|
||||||
thickness: number;
|
thickness: number;
|
||||||
@@ -19,14 +20,10 @@ export function ArmorMesh({
|
|||||||
isSpaced,
|
isSpaced,
|
||||||
isExternalModule = false,
|
isExternalModule = false,
|
||||||
thickness,
|
thickness,
|
||||||
|
duel,
|
||||||
...props
|
...props
|
||||||
}: ArmorMeshProps) {
|
}: ArmorMeshProps) {
|
||||||
const shell = useTankopedia((state) => {
|
const { shell } = duel.antagonist;
|
||||||
if (!state.areTanksAssigned) return;
|
|
||||||
return state.antagonist.shell;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!shell) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import usePromise from 'react-promise-suspense';
|
||||||
|
import { modelDefinitions } from '../blitzkrieg/modelDefinitions';
|
||||||
|
|
||||||
|
export function useModelDefinitions() {
|
||||||
|
return usePromise(async () => await modelDefinitions, []);
|
||||||
|
}
|
||||||
@@ -14,8 +14,13 @@ export type TankopediaSortBy = 'tier' | 'name';
|
|||||||
export type TankopediaSortDirection = 'ascending' | 'descending';
|
export type TankopediaSortDirection = 'ascending' | 'descending';
|
||||||
export type TankopediaTestTankDisplay = 'include' | 'exclude' | 'only';
|
export type TankopediaTestTankDisplay = 'include' | 'exclude' | 'only';
|
||||||
export type TankopediaMode = 'model' | 'armor';
|
export type TankopediaMode = 'model' | 'armor';
|
||||||
|
export interface DuelMember {
|
||||||
type Tankopedia = {
|
tank: TankDefinition;
|
||||||
|
turret: TurretDefinition;
|
||||||
|
gun: GunDefinition;
|
||||||
|
shell: ShellDefinition;
|
||||||
|
}
|
||||||
|
interface Tankopedia {
|
||||||
sort: {
|
sort: {
|
||||||
by: TankopediaSortBy;
|
by: TankopediaSortBy;
|
||||||
direction: TankopediaSortDirection;
|
direction: TankopediaSortDirection;
|
||||||
@@ -39,24 +44,7 @@ type Tankopedia = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
mode: TankopediaMode;
|
mode: TankopediaMode;
|
||||||
} & (
|
}
|
||||||
| {
|
|
||||||
areTanksAssigned: true;
|
|
||||||
protagonist: {
|
|
||||||
tank: TankDefinition;
|
|
||||||
turret: TurretDefinition;
|
|
||||||
gun: GunDefinition;
|
|
||||||
shell: ShellDefinition;
|
|
||||||
};
|
|
||||||
antagonist: {
|
|
||||||
tank: TankDefinition;
|
|
||||||
turret: TurretDefinition;
|
|
||||||
gun: GunDefinition;
|
|
||||||
shell: ShellDefinition;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
| { areTanksAssigned: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
export const useTankopedia = create<Tankopedia>()(
|
export const useTankopedia = create<Tankopedia>()(
|
||||||
subscribeWithSelector<Tankopedia>(() => ({
|
subscribeWithSelector<Tankopedia>(() => ({
|
||||||
|
|||||||
@@ -3715,6 +3715,11 @@ [email protected], fast-deep-equal@^3.1.3:
|
|||||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||||
|
|
||||||
|
fast-deep-equal@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
|
||||||
|
integrity sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==
|
||||||
|
|
||||||
[email protected], fast-glob@^3.3.2:
|
[email protected], fast-glob@^3.3.2:
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
|
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
|
||||||
@@ -5264,6 +5269,13 @@ react-merge-refs@^1.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06"
|
resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06"
|
||||||
integrity sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==
|
integrity sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==
|
||||||
|
|
||||||
|
react-promise-suspense@^0.3.4:
|
||||||
|
version "0.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-promise-suspense/-/react-promise-suspense-0.3.4.tgz#05d19a75703d71374674840056cfef2fcd38809d"
|
||||||
|
integrity sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==
|
||||||
|
dependencies:
|
||||||
|
fast-deep-equal "^2.0.1"
|
||||||
|
|
||||||
react-reconciler@^0.27.0:
|
react-reconciler@^0.27.0:
|
||||||
version "0.27.0"
|
version "0.27.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.27.0.tgz#360124fdf2d76447c7491ee5f0e04503ed9acf5b"
|
resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.27.0.tgz#360124fdf2d76447c7491ee5f0e04503ed9acf5b"
|
||||||
|
|||||||
Reference in new issue
Block a user