mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-25 11:39:51 +01:00
feat: upgrade rest of them
This commit is contained in:
1 parent
fe4afdcbf0
commit
30e87743fc
63 files changed
+824
-1123
No files matched your search
@@ -94,8 +94,12 @@ export class Varuna<Type, Arguments = void> {
|
|||||||
use<Slice = Type>(
|
use<Slice = Type>(
|
||||||
slicer: (state: Type) => Slice = (state) => state as unknown as Slice
|
slicer: (state: Type) => Slice = (state) => state as unknown as Slice
|
||||||
) {
|
) {
|
||||||
|
return this.useDeferred(slicer, slicer(this.state));
|
||||||
|
}
|
||||||
|
|
||||||
|
useDeferred<Slice = Type>(slicer: (state: Type) => Slice, initial: Slice) {
|
||||||
const instance = this;
|
const instance = this;
|
||||||
const [slice, setSlice] = useState(slicer(this.state));
|
const [slice, setSlice] = useState(initial);
|
||||||
|
|
||||||
useEffect(() => instance.on(slicer, setSlice), []);
|
useEffect(() => instance.on(slicer, setSlice), []);
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import { ArmorType } from "./SpacedArmorScene";
|
|||||||
export function ArmorPlateDisplay() {
|
export function ArmorPlateDisplay() {
|
||||||
const highlightArmor = Tankopedia.use((state) => state.highlightArmor);
|
const highlightArmor = Tankopedia.use((state) => state.highlightArmor);
|
||||||
const developerMode = App.useDeferred((state) => state.developerMode, false);
|
const developerMode = App.useDeferred((state) => state.developerMode, false);
|
||||||
const duelStore = Duel.useStore();
|
|
||||||
const input = useRef<HTMLInputElement>(null);
|
const input = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
if (highlightArmor === undefined) return null;
|
if (highlightArmor === undefined) return null;
|
||||||
@@ -93,7 +92,7 @@ export function ArmorPlateDisplay() {
|
|||||||
|
|
||||||
Tankopedia.mutate((draft) => {
|
Tankopedia.mutate((draft) => {
|
||||||
draft.highlightArmor = undefined;
|
draft.highlightArmor = undefined;
|
||||||
const { protagonist } = duelStore.getState();
|
const { protagonist } = Duel.state;
|
||||||
const tank = draft.model;
|
const tank = draft.model;
|
||||||
const turret = tank.turrets[protagonist.turret.id];
|
const turret = tank.turrets[protagonist.turret.id];
|
||||||
const gun = turret.guns[protagonist.gun.id];
|
const gun = turret.guns[protagonist.gun.id];
|
||||||
@@ -142,7 +141,7 @@ export function ArmorPlateDisplay() {
|
|||||||
|
|
||||||
draft.highlightArmor = undefined;
|
draft.highlightArmor = undefined;
|
||||||
const initialState = Tankopedia.initial;
|
const initialState = Tankopedia.initial;
|
||||||
const { protagonist } = duelStore.getState();
|
const { protagonist } = Duel.state;
|
||||||
const tank = draft.model;
|
const tank = draft.model;
|
||||||
const initialTank = initialState.model;
|
const initialTank = initialState.model;
|
||||||
const turret = tank.turrets[protagonist.turret.id];
|
const turret = tank.turrets[protagonist.turret.id];
|
||||||
|
|||||||
+27
-31
@@ -41,8 +41,6 @@ export function PrimaryArmorSceneComponent({
|
|||||||
node,
|
node,
|
||||||
thickness,
|
thickness,
|
||||||
}: PrimaryArmorSceneComponentProps) {
|
}: PrimaryArmorSceneComponentProps) {
|
||||||
const tankopediaPersistentStore = TankopediaPersistent.useStore();
|
|
||||||
const duelStore = Duel.useStore();
|
|
||||||
const material = new ShaderMaterial({
|
const material = new ShaderMaterial({
|
||||||
fragmentShader,
|
fragmentShader,
|
||||||
vertexShader,
|
vertexShader,
|
||||||
@@ -80,9 +78,9 @@ export function PrimaryArmorSceneComponent({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleShellChange() {
|
function handleShellChange() {
|
||||||
const duel = duelStore.getState();
|
|
||||||
const tankopediaEphemeral = Tankopedia.state;
|
const tankopediaEphemeral = Tankopedia.state;
|
||||||
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
|
const shell =
|
||||||
|
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||||
|
|
||||||
material.uniforms.caliber.value = shell.caliber;
|
material.uniforms.caliber.value = shell.caliber;
|
||||||
material.uniforms.ricochet.value = degToRad(
|
material.uniforms.ricochet.value = degToRad(
|
||||||
@@ -96,8 +94,14 @@ export function PrimaryArmorSceneComponent({
|
|||||||
material.uniforms.damage.value = shell.armor_damage;
|
material.uniforms.damage.value = shell.armor_damage;
|
||||||
material.uniforms.explosionRadius.value = shell.explosion_radius;
|
material.uniforms.explosionRadius.value = shell.explosion_radius;
|
||||||
|
|
||||||
handleProtagonistEquipmentChange(duel.protagonist.equipmentMatrix, true);
|
handleProtagonistEquipmentChange(
|
||||||
handleAntagonistEquipmentChange(duel.antagonist.equipmentMatrix, true);
|
Duel.state.protagonist.equipmentMatrix,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
handleAntagonistEquipmentChange(
|
||||||
|
Duel.state.antagonist.equipmentMatrix,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
@@ -118,10 +122,9 @@ export function PrimaryArmorSceneComponent({
|
|||||||
equipment: EquipmentMatrix,
|
equipment: EquipmentMatrix,
|
||||||
noInvalidate = false
|
noInvalidate = false
|
||||||
) {
|
) {
|
||||||
const duel = duelStore.getState();
|
|
||||||
const hasEnhancedArmor = hasEquipment(
|
const hasEnhancedArmor = hasEquipment(
|
||||||
110,
|
110,
|
||||||
duel.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
equipment
|
equipment
|
||||||
);
|
);
|
||||||
material.uniforms.thickness.value = hasEnhancedArmor
|
material.uniforms.thickness.value = hasEnhancedArmor
|
||||||
@@ -134,13 +137,13 @@ export function PrimaryArmorSceneComponent({
|
|||||||
equipment: EquipmentMatrix,
|
equipment: EquipmentMatrix,
|
||||||
noInvalidate = false
|
noInvalidate = false
|
||||||
) {
|
) {
|
||||||
const duel = duelStore.getState();
|
|
||||||
const tankopediaEphemeral = Tankopedia.state;
|
const tankopediaEphemeral = Tankopedia.state;
|
||||||
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
|
const shell =
|
||||||
|
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||||
const penetration = shell.penetration.near;
|
const penetration = shell.penetration.near;
|
||||||
const hasCalibratedShells = hasEquipment(
|
const hasCalibratedShells = hasEquipment(
|
||||||
103,
|
103,
|
||||||
duel.antagonist.tank.equipment_preset,
|
Duel.state.antagonist.tank.equipment_preset,
|
||||||
equipment
|
equipment
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -151,40 +154,33 @@ export function PrimaryArmorSceneComponent({
|
|||||||
if (!noInvalidate) invalidate();
|
if (!noInvalidate) invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState = tankopediaPersistentStore.getState();
|
|
||||||
|
|
||||||
handleShellChange();
|
handleShellChange();
|
||||||
handleGreenPenetrationChange(initialState.greenPenetration);
|
handleGreenPenetrationChange(TankopediaPersistent.state.greenPenetration);
|
||||||
handleAdvancedHighlightingChange(initialState.advancedHighlighting);
|
handleAdvancedHighlightingChange(
|
||||||
handleOpaqueChange(initialState.opaque);
|
TankopediaPersistent.state.advancedHighlighting
|
||||||
handleWireframeChange(initialState.wireframe);
|
|
||||||
handleProtagonistEquipmentChange(
|
|
||||||
duelStore.getState().protagonist.equipmentMatrix
|
|
||||||
);
|
|
||||||
handleAntagonistEquipmentChange(
|
|
||||||
duelStore.getState().antagonist.equipmentMatrix
|
|
||||||
);
|
);
|
||||||
|
handleOpaqueChange(TankopediaPersistent.state.opaque);
|
||||||
|
handleWireframeChange(TankopediaPersistent.state.wireframe);
|
||||||
|
handleProtagonistEquipmentChange(Duel.state.protagonist.equipmentMatrix);
|
||||||
|
handleAntagonistEquipmentChange(Duel.state.antagonist.equipmentMatrix);
|
||||||
|
|
||||||
const unsubscribes = [
|
const unsubscribes = [
|
||||||
duelStore.subscribe((state) => state.antagonist.shell, handleShellChange),
|
Duel.on((state) => state.antagonist.shell, handleShellChange),
|
||||||
Tankopedia.on((state) => state.customShell, handleShellChange),
|
Tankopedia.on((state) => state.customShell, handleShellChange),
|
||||||
tankopediaPersistentStore.subscribe(
|
TankopediaPersistent.on(
|
||||||
(state) => state.greenPenetration,
|
(state) => state.greenPenetration,
|
||||||
handleGreenPenetrationChange
|
handleGreenPenetrationChange
|
||||||
),
|
),
|
||||||
tankopediaPersistentStore.subscribe(
|
TankopediaPersistent.on(
|
||||||
(state) => state.advancedHighlighting,
|
(state) => state.advancedHighlighting,
|
||||||
handleAdvancedHighlightingChange
|
handleAdvancedHighlightingChange
|
||||||
),
|
),
|
||||||
tankopediaPersistentStore.subscribe(
|
TankopediaPersistent.on((state) => state.opaque, handleOpaqueChange),
|
||||||
(state) => state.opaque,
|
Duel.on(
|
||||||
handleOpaqueChange
|
|
||||||
),
|
|
||||||
duelStore.subscribe(
|
|
||||||
(state) => state.protagonist.equipmentMatrix,
|
(state) => state.protagonist.equipmentMatrix,
|
||||||
(equipment) => handleProtagonistEquipmentChange(equipment)
|
(equipment) => handleProtagonistEquipmentChange(equipment)
|
||||||
),
|
),
|
||||||
duelStore.subscribe(
|
Duel.on(
|
||||||
(state) => state.antagonist.equipmentMatrix,
|
(state) => state.antagonist.equipmentMatrix,
|
||||||
(equipment) => handleAntagonistEquipmentChange(equipment)
|
(equipment) => handleAntagonistEquipmentChange(equipment)
|
||||||
),
|
),
|
||||||
|
|||||||
+11
-17
@@ -29,7 +29,6 @@ export function SpacedArmorSubExternal({
|
|||||||
variant,
|
variant,
|
||||||
clip,
|
clip,
|
||||||
}: SpacedArmorSubExternalProps) {
|
}: SpacedArmorSubExternalProps) {
|
||||||
const duelStore = Duel.useStore();
|
|
||||||
const material = new ShaderMaterial({
|
const material = new ShaderMaterial({
|
||||||
fragmentShader,
|
fragmentShader,
|
||||||
vertexShader,
|
vertexShader,
|
||||||
@@ -48,16 +47,15 @@ export function SpacedArmorSubExternal({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleShellChange() {
|
function handleShellChange() {
|
||||||
const duel = duelStore.getState();
|
|
||||||
const tankopediaEphemeral = Tankopedia.state;
|
const tankopediaEphemeral = Tankopedia.state;
|
||||||
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
|
const shell =
|
||||||
|
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||||
material.uniforms.penetration.value = shell.penetration.near;
|
material.uniforms.penetration.value = shell.penetration.near;
|
||||||
}
|
}
|
||||||
function handleProtagonistEquipmentChange(equipment: EquipmentMatrix) {
|
function handleProtagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||||
const duel = duelStore.getState();
|
|
||||||
const hasEnhancedArmor = hasEquipment(
|
const hasEnhancedArmor = hasEquipment(
|
||||||
110,
|
110,
|
||||||
duel.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
equipment
|
equipment
|
||||||
);
|
);
|
||||||
material.uniforms.thickness.value = hasEnhancedArmor
|
material.uniforms.thickness.value = hasEnhancedArmor
|
||||||
@@ -65,13 +63,13 @@ export function SpacedArmorSubExternal({
|
|||||||
: thickness;
|
: thickness;
|
||||||
}
|
}
|
||||||
function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
|
function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||||
const duel = duelStore.getState();
|
|
||||||
const tankopediaEphemeral = Tankopedia.state;
|
const tankopediaEphemeral = Tankopedia.state;
|
||||||
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
|
const shell =
|
||||||
|
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||||
const penetration = shell.penetration.near;
|
const penetration = shell.penetration.near;
|
||||||
const hasCalibratedShells = hasEquipment(
|
const hasCalibratedShells = hasEquipment(
|
||||||
103,
|
103,
|
||||||
duel.antagonist.tank.equipment_preset,
|
Duel.state.antagonist.tank.equipment_preset,
|
||||||
equipment
|
equipment
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -81,21 +79,17 @@ export function SpacedArmorSubExternal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleShellChange();
|
handleShellChange();
|
||||||
handleProtagonistEquipmentChange(
|
handleProtagonistEquipmentChange(Duel.state.protagonist.equipmentMatrix);
|
||||||
duelStore.getState().protagonist.equipmentMatrix
|
handleAntagonistEquipmentChange(Duel.state.antagonist.equipmentMatrix);
|
||||||
);
|
|
||||||
handleAntagonistEquipmentChange(
|
|
||||||
duelStore.getState().antagonist.equipmentMatrix
|
|
||||||
);
|
|
||||||
|
|
||||||
const unsubscribes = [
|
const unsubscribes = [
|
||||||
duelStore.subscribe((state) => state.antagonist.shell, handleShellChange),
|
Duel.on((state) => state.antagonist.shell, handleShellChange),
|
||||||
Tankopedia.on((state) => state.customShell, handleShellChange),
|
Tankopedia.on((state) => state.customShell, handleShellChange),
|
||||||
duelStore.subscribe(
|
Duel.on(
|
||||||
(state) => state.protagonist.equipmentMatrix,
|
(state) => state.protagonist.equipmentMatrix,
|
||||||
handleProtagonistEquipmentChange
|
handleProtagonistEquipmentChange
|
||||||
),
|
),
|
||||||
duelStore.subscribe(
|
Duel.on(
|
||||||
(state) => state.antagonist.equipmentMatrix,
|
(state) => state.antagonist.equipmentMatrix,
|
||||||
handleAntagonistEquipmentChange
|
handleAntagonistEquipmentChange
|
||||||
),
|
),
|
||||||
|
|||||||
+11
-17
@@ -30,7 +30,6 @@ export function SpacedArmorSubSpaced({
|
|||||||
node,
|
node,
|
||||||
thickness,
|
thickness,
|
||||||
}: SpacedArmorSubSpacedProps) {
|
}: SpacedArmorSubSpacedProps) {
|
||||||
const duelStore = Duel.useStore();
|
|
||||||
const material = new ShaderMaterial({
|
const material = new ShaderMaterial({
|
||||||
fragmentShader,
|
fragmentShader,
|
||||||
vertexShader,
|
vertexShader,
|
||||||
@@ -50,9 +49,9 @@ export function SpacedArmorSubSpaced({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleShellChange() {
|
function handleShellChange() {
|
||||||
const duel = duelStore.getState();
|
|
||||||
const tankopediaEphemeral = Tankopedia.state;
|
const tankopediaEphemeral = Tankopedia.state;
|
||||||
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
|
const shell =
|
||||||
|
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||||
|
|
||||||
material.uniforms.penetration.value = shell.penetration.near;
|
material.uniforms.penetration.value = shell.penetration.near;
|
||||||
material.uniforms.caliber.value = shell.caliber;
|
material.uniforms.caliber.value = shell.caliber;
|
||||||
@@ -64,10 +63,9 @@ export function SpacedArmorSubSpaced({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
function handleProtagonistEquipmentChange(equipment: EquipmentMatrix) {
|
function handleProtagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||||
const duel = duelStore.getState();
|
|
||||||
const hasEnhancedArmor = hasEquipment(
|
const hasEnhancedArmor = hasEquipment(
|
||||||
110,
|
110,
|
||||||
duel.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
equipment
|
equipment
|
||||||
);
|
);
|
||||||
material.uniforms.thickness.value = hasEnhancedArmor
|
material.uniforms.thickness.value = hasEnhancedArmor
|
||||||
@@ -75,13 +73,13 @@ export function SpacedArmorSubSpaced({
|
|||||||
: thickness;
|
: thickness;
|
||||||
}
|
}
|
||||||
function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
|
function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||||
const duel = duelStore.getState();
|
|
||||||
const tankopediaEphemeral = Tankopedia.state;
|
const tankopediaEphemeral = Tankopedia.state;
|
||||||
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
|
const shell =
|
||||||
|
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||||
const penetration = shell.penetration.near;
|
const penetration = shell.penetration.near;
|
||||||
const hasCalibratedShells = hasEquipment(
|
const hasCalibratedShells = hasEquipment(
|
||||||
103,
|
103,
|
||||||
duel.antagonist.tank.equipment_preset,
|
Duel.state.antagonist.tank.equipment_preset,
|
||||||
equipment
|
equipment
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -91,21 +89,17 @@ export function SpacedArmorSubSpaced({
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleShellChange();
|
handleShellChange();
|
||||||
handleProtagonistEquipmentChange(
|
handleProtagonistEquipmentChange(Duel.state.protagonist.equipmentMatrix);
|
||||||
duelStore.getState().protagonist.equipmentMatrix
|
handleAntagonistEquipmentChange(Duel.state.antagonist.equipmentMatrix);
|
||||||
);
|
|
||||||
handleAntagonistEquipmentChange(
|
|
||||||
duelStore.getState().antagonist.equipmentMatrix
|
|
||||||
);
|
|
||||||
|
|
||||||
const unsubscribes = [
|
const unsubscribes = [
|
||||||
duelStore.subscribe((state) => state.antagonist.shell, handleShellChange),
|
Duel.on((state) => state.antagonist.shell, handleShellChange),
|
||||||
Tankopedia.on((state) => state.customShell, handleShellChange),
|
Tankopedia.on((state) => state.customShell, handleShellChange),
|
||||||
duelStore.subscribe(
|
Duel.on(
|
||||||
(state) => state.protagonist.equipmentMatrix,
|
(state) => state.protagonist.equipmentMatrix,
|
||||||
handleProtagonistEquipmentChange
|
handleProtagonistEquipmentChange
|
||||||
),
|
),
|
||||||
duelStore.subscribe(
|
Duel.on(
|
||||||
(state) => state.antagonist.equipmentMatrix,
|
(state) => state.antagonist.equipmentMatrix,
|
||||||
handleAntagonistEquipmentChange
|
handleAntagonistEquipmentChange
|
||||||
),
|
),
|
||||||
|
|||||||
+5
-7
@@ -76,7 +76,6 @@ export function SpacedArmorSceneComponent({
|
|||||||
clip,
|
clip,
|
||||||
...props
|
...props
|
||||||
}: SpacedArmorSceneComponentProps) {
|
}: SpacedArmorSceneComponentProps) {
|
||||||
const duelStore = Duel.useStore();
|
|
||||||
const camera = useThree((state) => state.camera);
|
const camera = useThree((state) => state.camera);
|
||||||
|
|
||||||
const shoot = useCallback(
|
const shoot = useCallback(
|
||||||
@@ -86,9 +85,8 @@ export function SpacedArmorSceneComponent({
|
|||||||
allowRicochet: boolean,
|
allowRicochet: boolean,
|
||||||
remainingPenetrationInput?: number
|
remainingPenetrationInput?: number
|
||||||
) => {
|
) => {
|
||||||
const { antagonist, protagonist } = duelStore.getState();
|
|
||||||
const { customShell } = Tankopedia.state;
|
const { customShell } = Tankopedia.state;
|
||||||
const shell = customShell ?? antagonist.shell;
|
const shell = customShell ?? Duel.state.antagonist.shell;
|
||||||
const cameraNormal = camera.position.clone().sub(point).normalize();
|
const cameraNormal = camera.position.clone().sub(point).normalize();
|
||||||
const shot: Shot = {
|
const shot: Shot = {
|
||||||
splashRadius:
|
splashRadius:
|
||||||
@@ -104,13 +102,13 @@ export function SpacedArmorSceneComponent({
|
|||||||
|
|
||||||
const hasCalibratedShells = hasEquipment(
|
const hasCalibratedShells = hasEquipment(
|
||||||
103,
|
103,
|
||||||
antagonist.tank.equipment_preset,
|
Duel.state.antagonist.tank.equipment_preset,
|
||||||
antagonist.equipmentMatrix
|
Duel.state.antagonist.equipmentMatrix
|
||||||
);
|
);
|
||||||
const hasEnhancedArmor = hasEquipment(
|
const hasEnhancedArmor = hasEquipment(
|
||||||
110,
|
110,
|
||||||
protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix
|
||||||
);
|
);
|
||||||
const penetration =
|
const penetration =
|
||||||
shell.penetration.near *
|
shell.penetration.near *
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
|
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
|
||||||
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
|
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
|
||||||
const canvas = useThree((state) => state.gl.domElement);
|
const canvas = useThree((state) => state.gl.domElement);
|
||||||
const duelStore = Duel.useStore();
|
|
||||||
const maskOrigin =
|
const maskOrigin =
|
||||||
gunModelDefinition.mask === undefined
|
gunModelDefinition.mask === undefined
|
||||||
? undefined
|
? undefined
|
||||||
@@ -165,16 +165,15 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
window.addEventListener("pointerup", handlePointerUp);
|
window.addEventListener("pointerup", handlePointerUp);
|
||||||
}
|
}
|
||||||
function handlePointerMove(event: PointerEvent) {
|
function handlePointerMove(event: PointerEvent) {
|
||||||
const duel = duelStore.getState();
|
|
||||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||||
122,
|
122,
|
||||||
duel.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
duel.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix
|
||||||
);
|
);
|
||||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||||
124,
|
124,
|
||||||
duel.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
duel.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix
|
||||||
);
|
);
|
||||||
const boundingRect = canvas.getBoundingClientRect();
|
const boundingRect = canvas.getBoundingClientRect();
|
||||||
|
|
||||||
@@ -258,16 +257,15 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
window.addEventListener("pointerup", handlePointerUp);
|
window.addEventListener("pointerup", handlePointerUp);
|
||||||
}
|
}
|
||||||
function handlePointerMove(event: PointerEvent) {
|
function handlePointerMove(event: PointerEvent) {
|
||||||
const duel = duelStore.getState();
|
|
||||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||||
122,
|
122,
|
||||||
duel.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
duel.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix
|
||||||
);
|
);
|
||||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||||
124,
|
124,
|
||||||
duel.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
duel.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix
|
||||||
);
|
);
|
||||||
const boundingRect = canvas.getBoundingClientRect();
|
const boundingRect = canvas.getBoundingClientRect();
|
||||||
delta.set(event.clientX, event.clientY).sub(position);
|
delta.set(event.clientX, event.clientY).sub(position);
|
||||||
@@ -339,16 +337,15 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
window.addEventListener("pointerup", handlePointerUp);
|
window.addEventListener("pointerup", handlePointerUp);
|
||||||
}
|
}
|
||||||
function handlePointerMove(event: PointerEvent) {
|
function handlePointerMove(event: PointerEvent) {
|
||||||
const duel = duelStore.getState();
|
|
||||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||||
122,
|
122,
|
||||||
duel.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
duel.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix
|
||||||
);
|
);
|
||||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||||
124,
|
124,
|
||||||
duel.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
duel.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix
|
||||||
);
|
);
|
||||||
const boundingRect = canvas.getBoundingClientRect();
|
const boundingRect = canvas.getBoundingClientRect();
|
||||||
delta.set(event.clientX, event.clientY).sub(position);
|
delta.set(event.clientX, event.clientY).sub(position);
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { assertSecret, idToRegion } from '@blitzkit/core';
|
import { assertSecret, idToRegion } from "@blitzkit/core";
|
||||||
import { Button, Flex, Heading, Text } from '@radix-ui/themes';
|
import { Button, Flex, Heading, Text } from "@radix-ui/themes";
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
LocaleProvider,
|
LocaleProvider,
|
||||||
useLocale,
|
useLocale,
|
||||||
type LocaleAcceptorProps,
|
type LocaleAcceptorProps,
|
||||||
} from '../../hooks/useLocale';
|
} from "../../hooks/useLocale";
|
||||||
import { App } from '../../stores/app';
|
import { App } from "../../stores/app";
|
||||||
import { CURRENT_POLICIES_AGREEMENT_INDEX } from '../../stores/app/constants';
|
import { CURRENT_POLICIES_AGREEMENT_INDEX } from "../../stores/app/constants";
|
||||||
import { LinkI18n } from '../LinkI18n';
|
import { LinkI18n } from "../LinkI18n";
|
||||||
import './index.css';
|
import "./index.css";
|
||||||
|
|
||||||
type Extension =
|
type Extension =
|
||||||
| {
|
| {
|
||||||
status: 'ok';
|
status: "ok";
|
||||||
data: {
|
data: {
|
||||||
access_token: string;
|
access_token: string;
|
||||||
account_id: number;
|
account_id: number;
|
||||||
@@ -21,15 +21,13 @@ type Extension =
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
status: 'error';
|
status: "error";
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Checks({ locale }: LocaleAcceptorProps) {
|
export function Checks({ locale }: LocaleAcceptorProps) {
|
||||||
return (
|
return (
|
||||||
<LocaleProvider locale={locale}>
|
<LocaleProvider locale={locale}>
|
||||||
<App.Provider>
|
<Content />
|
||||||
<Content />
|
|
||||||
</App.Provider>
|
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -40,19 +38,17 @@ export function Checks({ locale }: LocaleAcceptorProps) {
|
|||||||
function Content() {
|
function Content() {
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const logins = App.use((state) => state.logins);
|
const logins = App.use((state) => state.logins);
|
||||||
const mutateApp = App.useMutation();
|
|
||||||
const appStore = App.useStore();
|
|
||||||
const policiesAgreementIndex = App.use(
|
const policiesAgreementIndex = App.use(
|
||||||
(state) => state.policiesAgreementIndex,
|
(state) => state.policiesAgreementIndex
|
||||||
);
|
);
|
||||||
const isLegal = window.location.pathname.startsWith('/legal');
|
const isLegal = window.location.pathname.startsWith("/legal");
|
||||||
const [showPoliciesAgreement, setShowPoliciesAgreement] = useState(false);
|
const [showPoliciesAgreement, setShowPoliciesAgreement] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function unsubscribe() {
|
function unsubscribe() {
|
||||||
window.removeEventListener('scroll', show);
|
window.removeEventListener("scroll", show);
|
||||||
window.removeEventListener('pointermove', show);
|
window.removeEventListener("pointermove", show);
|
||||||
window.removeEventListener('pointerdown', show);
|
window.removeEventListener("pointerdown", show);
|
||||||
}
|
}
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
@@ -64,9 +60,9 @@ function Content() {
|
|||||||
policiesAgreementIndex !== CURRENT_POLICIES_AGREEMENT_INDEX &&
|
policiesAgreementIndex !== CURRENT_POLICIES_AGREEMENT_INDEX &&
|
||||||
!isLegal
|
!isLegal
|
||||||
) {
|
) {
|
||||||
window.addEventListener('scroll', show);
|
window.addEventListener("scroll", show);
|
||||||
window.addEventListener('pointermove', show);
|
window.addEventListener("pointermove", show);
|
||||||
window.addEventListener('pointerdown', show);
|
window.addEventListener("pointerdown", show);
|
||||||
}
|
}
|
||||||
|
|
||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
@@ -78,25 +74,25 @@ function Content() {
|
|||||||
const expiresInDays = expiresIn / 1000 / 60 / 60 / 24;
|
const expiresInDays = expiresIn / 1000 / 60 / 60 / 24;
|
||||||
|
|
||||||
if (expiresInDays < 0) {
|
if (expiresInDays < 0) {
|
||||||
mutateApp((draft) => {
|
App.mutate((draft) => {
|
||||||
draft.logins.wargaming = undefined;
|
draft.logins.wargaming = undefined;
|
||||||
});
|
});
|
||||||
} else if (expiresInDays < 7) {
|
} else if (expiresInDays < 7) {
|
||||||
const { wargaming } = appStore.getState().logins;
|
const { wargaming } = App.state.logins;
|
||||||
|
|
||||||
if (!wargaming) return;
|
if (!wargaming) return;
|
||||||
|
|
||||||
fetch(
|
fetch(
|
||||||
`https://api.worldoftanks.${idToRegion(
|
`https://api.worldoftanks.${idToRegion(
|
||||||
wargaming.id,
|
wargaming.id
|
||||||
)}/wot/auth/prolongate/?application_id=${assertSecret(
|
)}/wot/auth/prolongate/?application_id=${assertSecret(
|
||||||
import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID,
|
import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID
|
||||||
)}&access_token=${wargaming.token}`,
|
)}&access_token=${wargaming.token}`
|
||||||
)
|
)
|
||||||
.then((response) => response.json() as Promise<Extension>)
|
.then((response) => response.json() as Promise<Extension>)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
mutateApp((draft) => {
|
App.mutate((draft) => {
|
||||||
if (json.status === 'error') {
|
if (json.status === "error") {
|
||||||
draft.logins.wargaming = undefined;
|
draft.logins.wargaming = undefined;
|
||||||
} else {
|
} else {
|
||||||
draft.logins.wargaming = {
|
draft.logins.wargaming = {
|
||||||
@@ -126,11 +122,11 @@ function Content() {
|
|||||||
|
|
||||||
<Text>
|
<Text>
|
||||||
This website utilizes cookies to perform analytics and personalize
|
This website utilizes cookies to perform analytics and personalize
|
||||||
your experience. You can learn more through{' '}
|
your experience. You can learn more through{" "}
|
||||||
<LinkI18n locale={locale} href="/docs/legal/privacy-policy">
|
<LinkI18n locale={locale} href="/docs/legal/privacy-policy">
|
||||||
our privacy policy
|
our privacy policy
|
||||||
</LinkI18n>
|
</LinkI18n>
|
||||||
. By using BlitzKit, you also agree to our{' '}
|
. By using BlitzKit, you also agree to our{" "}
|
||||||
<LinkI18n locale={locale} href="/docs/legal/terms-of-service">
|
<LinkI18n locale={locale} href="/docs/legal/terms-of-service">
|
||||||
terms of service
|
terms of service
|
||||||
</LinkI18n>
|
</LinkI18n>
|
||||||
@@ -141,8 +137,8 @@ function Content() {
|
|||||||
<Button
|
<Button
|
||||||
mt="2"
|
mt="2"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
appStore.setState({
|
App.mutate((draft) => {
|
||||||
policiesAgreementIndex: CURRENT_POLICIES_AGREEMENT_INDEX,
|
draft.policiesAgreementIndex = CURRENT_POLICIES_AGREEMENT_INDEX;
|
||||||
});
|
});
|
||||||
setShowPoliciesAgreement(false);
|
setShowPoliciesAgreement(false);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { Switch } from '@radix-ui/themes';
|
import { Switch } from "@radix-ui/themes";
|
||||||
import type { EmbedPreviewControllerProps } from '../../pages/[...locale]/embed/[embed]/_index';
|
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
|
||||||
import { EmbedState } from '../../stores/embedState';
|
import { EmbedState } from "../../stores/embedState";
|
||||||
|
|
||||||
export function Boolean({ configKey }: EmbedPreviewControllerProps) {
|
export function Boolean({ configKey }: EmbedPreviewControllerProps) {
|
||||||
const mutateEmbedState = EmbedState.useMutation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch
|
<Switch
|
||||||
variant="classic"
|
variant="classic"
|
||||||
checked={EmbedState.use((state) => state[configKey] as boolean)}
|
checked={EmbedState.use((state) => state[configKey] as boolean)}
|
||||||
onCheckedChange={() => {
|
onCheckedChange={() => {
|
||||||
mutateEmbedState((draft) => {
|
EmbedState.mutate((draft) => {
|
||||||
draft[configKey] = !draft[configKey];
|
draft[configKey] = !draft[configKey];
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import type { EmbedPreviewControllerProps } from '../../pages/[...locale]/embed/[embed]/_index';
|
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
|
||||||
import { EmbedState } from '../../stores/embedState';
|
import { EmbedState } from "../../stores/embedState";
|
||||||
import { ColorControllerRaw } from './ColorControllerRaw';
|
import { ColorControllerRaw } from "./ColorControllerRaw";
|
||||||
|
|
||||||
export function Color({ configKey }: EmbedPreviewControllerProps) {
|
export function Color({ configKey }: EmbedPreviewControllerProps) {
|
||||||
const mutateEmbedState = EmbedState.useMutation();
|
|
||||||
const color = EmbedState.use((state) => state[configKey] as string);
|
const color = EmbedState.use((state) => state[configKey] as string);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ColorControllerRaw
|
<ColorControllerRaw
|
||||||
value={color}
|
value={color}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
mutateEmbedState((draft) => {
|
EmbedState.mutate((draft) => {
|
||||||
draft[configKey] = value;
|
draft[configKey] = value;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Select } from '@radix-ui/themes';
|
import { Select } from "@radix-ui/themes";
|
||||||
import { useLocale } from '../../hooks/useLocale';
|
import { useLocale } from "../../hooks/useLocale";
|
||||||
import type { EmbedPreviewControllerProps } from '../../pages/[...locale]/embed/[embed]/_index';
|
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
|
||||||
import { EmbedState } from '../../stores/embedState';
|
import { EmbedState } from "../../stores/embedState";
|
||||||
import type {
|
import type {
|
||||||
EmbedConfigItemType,
|
EmbedConfigItemType,
|
||||||
EmbedItemType,
|
EmbedItemType,
|
||||||
} from '../../stores/embedState/constants';
|
} from "../../stores/embedState/constants";
|
||||||
|
|
||||||
export function Enum({
|
export function Enum({
|
||||||
configKey,
|
configKey,
|
||||||
@@ -13,14 +13,13 @@ export function Enum({
|
|||||||
}: EmbedPreviewControllerProps & {
|
}: EmbedPreviewControllerProps & {
|
||||||
config: EmbedConfigItemType<EmbedItemType.Enum>;
|
config: EmbedConfigItemType<EmbedItemType.Enum>;
|
||||||
}) {
|
}) {
|
||||||
const mutateEmbedState = EmbedState.useMutation();
|
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select.Root
|
<Select.Root
|
||||||
value={EmbedState.use((state) => state[configKey] as string)}
|
value={EmbedState.use((state) => state[configKey] as string)}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
mutateEmbedState((draft) => {
|
EmbedState.mutate((draft) => {
|
||||||
draft[configKey] = value;
|
draft[configKey] = value;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import {
|
|||||||
searchPlayersAcrossRegions,
|
searchPlayersAcrossRegions,
|
||||||
type AccountListItem,
|
type AccountListItem,
|
||||||
type AccountListWithServer,
|
type AccountListWithServer,
|
||||||
} from '@blitzkit/core';
|
} from "@blitzkit/core";
|
||||||
import { literals } from '@blitzkit/i18n/src/literals';
|
import { literals } from "@blitzkit/i18n/src/literals";
|
||||||
import { CaretLeftIcon, Link2Icon, PersonIcon } from '@radix-ui/react-icons';
|
import { CaretLeftIcon, Link2Icon, PersonIcon } from "@radix-ui/react-icons";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -13,15 +13,15 @@ import {
|
|||||||
Spinner,
|
Spinner,
|
||||||
Text,
|
Text,
|
||||||
TextField,
|
TextField,
|
||||||
} from '@radix-ui/themes';
|
} from "@radix-ui/themes";
|
||||||
import { debounce } from 'lodash-es';
|
import { debounce } from "lodash-es";
|
||||||
import { useCallback, useRef, useState } from 'react';
|
import { useCallback, useRef, useState } from "react";
|
||||||
import { stringify } from 'urlon';
|
import { stringify } from "urlon";
|
||||||
import type { embedConfigurations } from '../../constants/embeds';
|
import type { embedConfigurations } from "../../constants/embeds";
|
||||||
import { useLocale } from '../../hooks/useLocale';
|
import { useLocale } from "../../hooks/useLocale";
|
||||||
import { EmbedState, type EmbedStateStore } from '../../stores/embedState';
|
import { EmbedState, type EmbedStateStore } from "../../stores/embedState";
|
||||||
import { CopyButton } from '../CopyButton';
|
import { CopyButton } from "../CopyButton";
|
||||||
import { SearchResults } from '../SearchResults';
|
import { SearchResults } from "../SearchResults";
|
||||||
|
|
||||||
interface GenerateURLProps {
|
interface GenerateURLProps {
|
||||||
embed: keyof typeof embedConfigurations;
|
embed: keyof typeof embedConfigurations;
|
||||||
@@ -31,7 +31,6 @@ export function GenerateURL({ embed }: GenerateURLProps) {
|
|||||||
const [searchDialogOpen, setSearchDialogOpen] = useState(false);
|
const [searchDialogOpen, setSearchDialogOpen] = useState(false);
|
||||||
const [copyDialogOpen, setCopyDialogOpen] = useState(false);
|
const [copyDialogOpen, setCopyDialogOpen] = useState(false);
|
||||||
const [copyUser, setCopyUser] = useState<AccountListItem | null>(null);
|
const [copyUser, setCopyUser] = useState<AccountListItem | null>(null);
|
||||||
const embedStateStore = EmbedState.useStore();
|
|
||||||
const input = useRef<HTMLInputElement>(null);
|
const input = useRef<HTMLInputElement>(null);
|
||||||
const [searching, setSearching] = useState(false);
|
const [searching, setSearching] = useState(false);
|
||||||
const [results, setResults] = useState<AccountListWithServer>([]);
|
const [results, setResults] = useState<AccountListWithServer>([]);
|
||||||
@@ -52,7 +51,7 @@ export function GenerateURL({ embed }: GenerateURLProps) {
|
|||||||
setSearching(false);
|
setSearching(false);
|
||||||
setResults(results);
|
setResults(results);
|
||||||
}, 500),
|
}, 500),
|
||||||
[],
|
[]
|
||||||
);
|
);
|
||||||
const handleChange = useCallback(() => {
|
const handleChange = useCallback(() => {
|
||||||
if (!input.current) return;
|
if (!input.current) return;
|
||||||
@@ -94,7 +93,7 @@ export function GenerateURL({ embed }: GenerateURLProps) {
|
|||||||
<TextField.Root
|
<TextField.Root
|
||||||
variant="classic"
|
variant="classic"
|
||||||
mt="4"
|
mt="4"
|
||||||
style={{ width: '20rem' }}
|
style={{ width: "20rem" }}
|
||||||
placeholder={strings.website.common.player_search.hint}
|
placeholder={strings.website.common.player_search.hint}
|
||||||
ref={input}
|
ref={input}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@@ -136,7 +135,7 @@ export function GenerateURL({ embed }: GenerateURLProps) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Dialog.Close>
|
<Dialog.Close>
|
||||||
<Button color="red" variant="outline" style={{ width: '100%' }}>
|
<Button color="red" variant="outline" style={{ width: "100%" }}>
|
||||||
{strings.website.tools.embed.configuration.export.generate.cancel}
|
{strings.website.tools.embed.configuration.export.generate.cancel}
|
||||||
</Button>
|
</Button>
|
||||||
</Dialog.Close>
|
</Dialog.Close>
|
||||||
@@ -144,7 +143,7 @@ export function GenerateURL({ embed }: GenerateURLProps) {
|
|||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
|
|
||||||
<Dialog.Root open={copyDialogOpen} onOpenChange={setCopyDialogOpen}>
|
<Dialog.Root open={copyDialogOpen} onOpenChange={setCopyDialogOpen}>
|
||||||
<Dialog.Content style={{ width: 'fit-content' }}>
|
<Dialog.Content style={{ width: "fit-content" }}>
|
||||||
<Flex direction="column" gap="1">
|
<Flex direction="column" gap="1">
|
||||||
<Heading size="5">
|
<Heading size="5">
|
||||||
{
|
{
|
||||||
@@ -156,7 +155,7 @@ export function GenerateURL({ embed }: GenerateURLProps) {
|
|||||||
{literals(
|
{literals(
|
||||||
strings.website.tools.embed.configuration.export.generate
|
strings.website.tools.embed.configuration.export.generate
|
||||||
.success.info,
|
.success.info,
|
||||||
[`${copyUser?.nickname}`],
|
[`${copyUser?.nickname}`]
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -170,7 +169,7 @@ export function GenerateURL({ embed }: GenerateURLProps) {
|
|||||||
setSearchDialogOpen(true);
|
setSearchDialogOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CaretLeftIcon />{' '}
|
<CaretLeftIcon />{" "}
|
||||||
{
|
{
|
||||||
strings.website.tools.embed.configuration.export.generate
|
strings.website.tools.embed.configuration.export.generate
|
||||||
.success.back
|
.success.back
|
||||||
@@ -181,12 +180,10 @@ export function GenerateURL({ embed }: GenerateURLProps) {
|
|||||||
copy={() => {
|
copy={() => {
|
||||||
if (!copyUser) return;
|
if (!copyUser) return;
|
||||||
|
|
||||||
const state = embedStateStore.getState();
|
|
||||||
const initial = embedStateStore.getInitialState();
|
|
||||||
const shallowState: EmbedStateStore = {};
|
const shallowState: EmbedStateStore = {};
|
||||||
|
|
||||||
Object.entries(state).forEach(([key, value]) => {
|
Object.entries(EmbedState.state).forEach(([key, value]) => {
|
||||||
if (value !== initial[key]) {
|
if (value !== EmbedState.initial[key]) {
|
||||||
shallowState[key] = value;
|
shallowState[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -202,7 +199,7 @@ export function GenerateURL({ embed }: GenerateURLProps) {
|
|||||||
return `${location.origin}/embed/${embed}/host?${searchParams.toString()}`;
|
return `${location.origin}/embed/${embed}/host?${searchParams.toString()}`;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Link2Icon />{' '}
|
<Link2Icon />{" "}
|
||||||
{
|
{
|
||||||
strings.website.tools.embed.configuration.export.generate
|
strings.website.tools.embed.configuration.export.generate
|
||||||
.success.copy
|
.success.copy
|
||||||
|
|||||||
@@ -6,17 +6,16 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
TextField,
|
TextField,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@radix-ui/themes';
|
} from "@radix-ui/themes";
|
||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from "react";
|
||||||
import { parse } from 'urlon';
|
import { parse } from "urlon";
|
||||||
import { useLocale } from '../../hooks/useLocale';
|
import { useLocale } from "../../hooks/useLocale";
|
||||||
import { EmbedState } from '../../stores/embedState';
|
import { EmbedState } from "../../stores/embedState";
|
||||||
|
|
||||||
export function Import() {
|
export function Import() {
|
||||||
const importInput = useRef<HTMLInputElement>(null);
|
const importInput = useRef<HTMLInputElement>(null);
|
||||||
const [showInvalidURLPrompt, setShowInvalidURLPrompt] = useState(false);
|
const [showInvalidURLPrompt, setShowInvalidURLPrompt] = useState(false);
|
||||||
const [showConfirmation, setConfirmation] = useState(false);
|
const [showConfirmation, setConfirmation] = useState(false);
|
||||||
const mutateEmbedState = EmbedState.useMutation();
|
|
||||||
const { strings } = useLocale();
|
const { strings } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -64,15 +63,15 @@ export function Import() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const url = new URL(importInput.current.value);
|
const url = new URL(importInput.current.value);
|
||||||
const state = url.searchParams.get('state');
|
const state = url.searchParams.get("state");
|
||||||
if (state === null) throw undefined;
|
if (state === null) throw undefined;
|
||||||
const json = parse(state);
|
const json = parse(state);
|
||||||
|
|
||||||
mutateEmbedState((draft) => {
|
EmbedState.mutate((draft) => {
|
||||||
Object.assign(draft, json);
|
Object.assign(draft, json);
|
||||||
});
|
});
|
||||||
|
|
||||||
importInput.current.value = '';
|
importInput.current.value = "";
|
||||||
setConfirmation(true);
|
setConfirmation(true);
|
||||||
setTimeout(() => setConfirmation(false), 2000);
|
setTimeout(() => setConfirmation(false), 2000);
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { Slider } from '@radix-ui/themes';
|
import { Slider } from "@radix-ui/themes";
|
||||||
import type { EmbedPreviewControllerProps } from '../../pages/[...locale]/embed/[embed]/_index';
|
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
|
||||||
import { EmbedState, type RadixRadius } from '../../stores/embedState';
|
import { EmbedState, type RadixRadius } from "../../stores/embedState";
|
||||||
|
|
||||||
export function Radius({ configKey }: EmbedPreviewControllerProps) {
|
export function Radius({ configKey }: EmbedPreviewControllerProps) {
|
||||||
const mutateEmbedState = EmbedState.useMutation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Slider
|
<Slider
|
||||||
variant="classic"
|
variant="classic"
|
||||||
@@ -13,12 +11,12 @@ export function Radius({ configKey }: EmbedPreviewControllerProps) {
|
|||||||
value={[
|
value={[
|
||||||
EmbedState.use((state) => {
|
EmbedState.use((state) => {
|
||||||
const radius = state[configKey] as RadixRadius;
|
const radius = state[configKey] as RadixRadius;
|
||||||
return radius === 'full' ? 5 : parseInt(radius);
|
return radius === "full" ? 5 : parseInt(radius);
|
||||||
}),
|
}),
|
||||||
]}
|
]}
|
||||||
onValueChange={([value]) => {
|
onValueChange={([value]) => {
|
||||||
mutateEmbedState((draft) => {
|
EmbedState.mutate((draft) => {
|
||||||
draft[configKey] = value === 5 ? 'full' : `${value}`;
|
draft[configKey] = value === 5 ? "full" : `${value}`;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
import { FontBoldIcon, FontSizeIcon } from '@radix-ui/react-icons';
|
import { FontBoldIcon, FontSizeIcon } from "@radix-ui/react-icons";
|
||||||
import { Flex, Slider, Text } from '@radix-ui/themes';
|
import { Flex, Slider, Text } from "@radix-ui/themes";
|
||||||
import type { EmbedPreviewControllerProps } from '../../pages/[...locale]/embed/[embed]/_index';
|
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
|
||||||
import { EmbedState, type RadixSizeWithout0 } from '../../stores/embedState';
|
import { EmbedState, type RadixSizeWithout0 } from "../../stores/embedState";
|
||||||
import {
|
import {
|
||||||
radixTextWeights,
|
radixTextWeights,
|
||||||
type EmbedConfigItemType,
|
type EmbedConfigItemType,
|
||||||
type EmbedItemType,
|
type EmbedItemType,
|
||||||
} from '../../stores/embedState/constants';
|
} from "../../stores/embedState/constants";
|
||||||
import { ColorControllerRaw } from './ColorControllerRaw';
|
import { ColorControllerRaw } from "./ColorControllerRaw";
|
||||||
|
|
||||||
export function RichText({ configKey }: EmbedPreviewControllerProps) {
|
export function RichText({ configKey }: EmbedPreviewControllerProps) {
|
||||||
const mutateEmbedState = EmbedState.useMutation();
|
|
||||||
const state = EmbedState.use(
|
const state = EmbedState.use(
|
||||||
(state) =>
|
(state) =>
|
||||||
state[
|
state[configKey] as EmbedConfigItemType<EmbedItemType.RichText>["default"]
|
||||||
configKey
|
|
||||||
] as EmbedConfigItemType<EmbedItemType.RichText>['default'],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -24,11 +21,11 @@ export function RichText({ configKey }: EmbedPreviewControllerProps) {
|
|||||||
<ColorControllerRaw
|
<ColorControllerRaw
|
||||||
value={state.color}
|
value={state.color}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
mutateEmbedState((draft) => {
|
EmbedState.mutate((draft) => {
|
||||||
(
|
(
|
||||||
draft[
|
draft[
|
||||||
configKey
|
configKey
|
||||||
] as EmbedConfigItemType<EmbedItemType.RichText>['default']
|
] as EmbedConfigItemType<EmbedItemType.RichText>["default"]
|
||||||
).color = value;
|
).color = value;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -45,11 +42,11 @@ export function RichText({ configKey }: EmbedPreviewControllerProps) {
|
|||||||
max={9}
|
max={9}
|
||||||
value={[Number(state.size)]}
|
value={[Number(state.size)]}
|
||||||
onValueChange={([value]) => {
|
onValueChange={([value]) => {
|
||||||
mutateEmbedState((draft) => {
|
EmbedState.mutate((draft) => {
|
||||||
(
|
(
|
||||||
draft[
|
draft[
|
||||||
configKey
|
configKey
|
||||||
] as EmbedConfigItemType<EmbedItemType.RichText>['default']
|
] as EmbedConfigItemType<EmbedItemType.RichText>["default"]
|
||||||
).size = `${value}` as RadixSizeWithout0;
|
).size = `${value}` as RadixSizeWithout0;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -66,11 +63,11 @@ export function RichText({ configKey }: EmbedPreviewControllerProps) {
|
|||||||
max={radixTextWeights.length - 1}
|
max={radixTextWeights.length - 1}
|
||||||
value={[radixTextWeights.indexOf(state.weight)]}
|
value={[radixTextWeights.indexOf(state.weight)]}
|
||||||
onValueChange={([value]) => {
|
onValueChange={([value]) => {
|
||||||
mutateEmbedState((draft) => {
|
EmbedState.mutate((draft) => {
|
||||||
(
|
(
|
||||||
draft[
|
draft[
|
||||||
configKey
|
configKey
|
||||||
] as EmbedConfigItemType<EmbedItemType.RichText>['default']
|
] as EmbedConfigItemType<EmbedItemType.RichText>["default"]
|
||||||
).weight = radixTextWeights[value];
|
).weight = radixTextWeights[value];
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { Slider } from '@radix-ui/themes';
|
import { Slider } from "@radix-ui/themes";
|
||||||
import type { EmbedPreviewControllerProps } from '../../pages/[...locale]/embed/[embed]/_index';
|
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
|
||||||
import { EmbedState, type RadixSize } from '../../stores/embedState';
|
import { EmbedState, type RadixSize } from "../../stores/embedState";
|
||||||
|
|
||||||
export function Size({ configKey }: EmbedPreviewControllerProps) {
|
export function Size({ configKey }: EmbedPreviewControllerProps) {
|
||||||
const mutateEmbedState = EmbedState.useMutation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Slider
|
<Slider
|
||||||
variant="classic"
|
variant="classic"
|
||||||
@@ -14,7 +12,7 @@ export function Size({ configKey }: EmbedPreviewControllerProps) {
|
|||||||
EmbedState.use((state) => parseInt(state[configKey] as RadixSize)),
|
EmbedState.use((state) => parseInt(state[configKey] as RadixSize)),
|
||||||
]}
|
]}
|
||||||
onValueChange={([value]) => {
|
onValueChange={([value]) => {
|
||||||
mutateEmbedState((draft) => {
|
EmbedState.mutate((draft) => {
|
||||||
draft[configKey] = `${value}`;
|
draft[configKey] = `${value}`;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { Slider } from '@radix-ui/themes';
|
import { Slider } from "@radix-ui/themes";
|
||||||
import type { EmbedPreviewControllerProps } from '../../pages/[...locale]/embed/[embed]/_index';
|
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
|
||||||
import { EmbedState, type RadixSizeWithout0 } from '../../stores/embedState';
|
import { EmbedState, type RadixSizeWithout0 } from "../../stores/embedState";
|
||||||
|
|
||||||
export function SizeWithout0({ configKey }: EmbedPreviewControllerProps) {
|
export function SizeWithout0({ configKey }: EmbedPreviewControllerProps) {
|
||||||
const mutateEmbedState = EmbedState.useMutation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Slider
|
<Slider
|
||||||
variant="classic"
|
variant="classic"
|
||||||
@@ -12,11 +10,11 @@ export function SizeWithout0({ configKey }: EmbedPreviewControllerProps) {
|
|||||||
max={9}
|
max={9}
|
||||||
value={[
|
value={[
|
||||||
EmbedState.use((state) =>
|
EmbedState.use((state) =>
|
||||||
parseInt(state[configKey] as RadixSizeWithout0),
|
parseInt(state[configKey] as RadixSizeWithout0)
|
||||||
),
|
),
|
||||||
]}
|
]}
|
||||||
onValueChange={([value]) => {
|
onValueChange={([value]) => {
|
||||||
mutateEmbedState((draft) => {
|
EmbedState.mutate((draft) => {
|
||||||
draft[configKey] = `${value}`;
|
draft[configKey] = `${value}`;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Slider as RadixSlider } from '@radix-ui/themes';
|
import { Slider as RadixSlider } from "@radix-ui/themes";
|
||||||
import type { EmbedPreviewControllerProps } from '../../pages/[...locale]/embed/[embed]/_index';
|
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
|
||||||
import { EmbedState, type RadixSize } from '../../stores/embedState';
|
import { EmbedState, type RadixSize } from "../../stores/embedState";
|
||||||
import type {
|
import type {
|
||||||
EmbedConfigItemType,
|
EmbedConfigItemType,
|
||||||
EmbedItemType,
|
EmbedItemType,
|
||||||
} from '../../stores/embedState/constants';
|
} from "../../stores/embedState/constants";
|
||||||
|
|
||||||
export function Slider({
|
export function Slider({
|
||||||
configKey,
|
configKey,
|
||||||
@@ -12,8 +12,6 @@ export function Slider({
|
|||||||
}: EmbedPreviewControllerProps & {
|
}: EmbedPreviewControllerProps & {
|
||||||
config: EmbedConfigItemType<EmbedItemType.Slider>;
|
config: EmbedConfigItemType<EmbedItemType.Slider>;
|
||||||
}) {
|
}) {
|
||||||
const mutateEmbedState = EmbedState.useMutation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RadixSlider
|
<RadixSlider
|
||||||
variant="classic"
|
variant="classic"
|
||||||
@@ -23,7 +21,7 @@ export function Slider({
|
|||||||
EmbedState.use((state) => parseInt(state[configKey] as RadixSize)),
|
EmbedState.use((state) => parseInt(state[configKey] as RadixSize)),
|
||||||
]}
|
]}
|
||||||
onValueChange={([value]) => {
|
onValueChange={([value]) => {
|
||||||
mutateEmbedState((draft) => {
|
EmbedState.mutate((draft) => {
|
||||||
draft[configKey] = value;
|
draft[configKey] = value;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import { TextField } from '@radix-ui/themes';
|
import { TextField } from "@radix-ui/themes";
|
||||||
import type { EmbedPreviewControllerProps } from '../../pages/[...locale]/embed/[embed]/_index';
|
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
|
||||||
import { EmbedState } from '../../stores/embedState';
|
import { EmbedState } from "../../stores/embedState";
|
||||||
|
|
||||||
export function TextController({ configKey }: EmbedPreviewControllerProps) {
|
export function TextController({ configKey }: EmbedPreviewControllerProps) {
|
||||||
const mutateEmbedState = EmbedState.useMutation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TextField.Root
|
<TextField.Root
|
||||||
variant="classic"
|
variant="classic"
|
||||||
placeholder="Empty"
|
placeholder="Empty"
|
||||||
value={EmbedState.use((state) => state[configKey] as string)}
|
value={EmbedState.use((state) => state[configKey] as string)}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
mutateEmbedState((draft) => {
|
EmbedState.mutate((draft) => {
|
||||||
draft[configKey] = event.target.value;
|
draft[configKey] = event.target.value;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -17,10 +17,9 @@ const [modelDefinitions, provisionDefinitions] = await Promise.all([
|
|||||||
export function GuessBackground() {
|
export function GuessBackground() {
|
||||||
const guessState = Guess.use((state) => state.guessState);
|
const guessState = Guess.use((state) => state.guessState);
|
||||||
const tank = Guess.use((state) => state.tank);
|
const tank = Guess.use((state) => state.tank);
|
||||||
const mutateDuel = Duel.useMutation();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mutateDuel((state) => {
|
Duel.mutate((state) => {
|
||||||
state.protagonist = tankToDuelMember(tank, provisionDefinitions);
|
state.protagonist = tankToDuelMember(tank, provisionDefinitions);
|
||||||
});
|
});
|
||||||
Tankopedia.mutate((state) => {
|
Tankopedia.mutate((state) => {
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { literals } from '@blitzkit/i18n/src/literals';
|
import { literals } from "@blitzkit/i18n/src/literals";
|
||||||
import { EyeOpenIcon } from '@radix-ui/react-icons';
|
import { EyeOpenIcon } from "@radix-ui/react-icons";
|
||||||
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
|
import { Box, Flex, Heading, Text } from "@radix-ui/themes";
|
||||||
import { awaitablePopularTanks } from '../core/awaitables/popularTanks';
|
import { awaitablePopularTanks } from "../core/awaitables/popularTanks";
|
||||||
import { awaitableTankDefinitions } from '../core/awaitables/tankDefinitions';
|
import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions";
|
||||||
import {
|
import {
|
||||||
LocaleProvider,
|
LocaleProvider,
|
||||||
useLocale,
|
useLocale,
|
||||||
type LocaleAcceptorProps,
|
type LocaleAcceptorProps,
|
||||||
} from '../hooks/useLocale';
|
} from "../hooks/useLocale";
|
||||||
import { TankopediaPersistent } from '../stores/tankopediaPersistent';
|
import { TankCard } from "./TankCard";
|
||||||
import { TankCard } from './TankCard';
|
|
||||||
|
|
||||||
const [tankDefinitions, popularTanks] = await Promise.all([
|
const [tankDefinitions, popularTanks] = await Promise.all([
|
||||||
awaitableTankDefinitions,
|
awaitableTankDefinitions,
|
||||||
@@ -19,9 +18,7 @@ const [tankDefinitions, popularTanks] = await Promise.all([
|
|||||||
export function HomePageHotTanks({ locale }: LocaleAcceptorProps) {
|
export function HomePageHotTanks({ locale }: LocaleAcceptorProps) {
|
||||||
return (
|
return (
|
||||||
<LocaleProvider locale={locale}>
|
<LocaleProvider locale={locale}>
|
||||||
<TankopediaPersistent.Provider>
|
<Content />
|
||||||
<Content />
|
|
||||||
</TankopediaPersistent.Provider>
|
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -35,7 +32,7 @@ function Content() {
|
|||||||
{strings.website.home.hot_tanks.title}
|
{strings.website.home.hot_tanks.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text color="gray" align="center" mb="5" size="2">
|
<Text color="gray" align="center" mb="5" size="2">
|
||||||
{literals(strings.website.home.hot_tanks.subtitle, ['7'])}
|
{literals(strings.website.home.hot_tanks.subtitle, ["7"])}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Flex justify="center" gap="4" wrap="wrap">
|
<Flex justify="center" gap="4" wrap="wrap">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PersonIcon } from '@radix-ui/react-icons';
|
import { PersonIcon } from "@radix-ui/react-icons";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Flex,
|
Flex,
|
||||||
@@ -6,91 +6,78 @@ import {
|
|||||||
Popover,
|
Popover,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Text,
|
Text,
|
||||||
} from '@radix-ui/themes';
|
} from "@radix-ui/themes";
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from "react";
|
||||||
import { App } from '../../stores/app';
|
import { App } from "../../stores/app";
|
||||||
import { BlitzKitTheme } from '../BlitzKitTheme';
|
import { BlitzKitTheme } from "../BlitzKitTheme";
|
||||||
import { WargamingAccountName } from '../WargamingAccountName';
|
import { WargamingAccountName } from "../WargamingAccountName";
|
||||||
import { WargamingIcon } from '../WargamingIcon';
|
import { WargamingIcon } from "../WargamingIcon";
|
||||||
import { WargamingLoginButton } from '../WargamingLoginButton';
|
import { WargamingLoginButton } from "../WargamingLoginButton";
|
||||||
|
|
||||||
export function NavbarAccount() {
|
export function NavbarAccount() {
|
||||||
return (
|
|
||||||
<App.Provider>
|
|
||||||
<Content />
|
|
||||||
</App.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Content() {
|
|
||||||
const logins = App.use((state) => state.logins);
|
const logins = App.use((state) => state.logins);
|
||||||
const mutateApp = App.useMutation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<App.Provider>
|
<Popover.Root>
|
||||||
<Popover.Root>
|
<Popover.Trigger>
|
||||||
<Popover.Trigger>
|
<IconButton variant="ghost" color="gray">
|
||||||
<IconButton variant="ghost" color="gray">
|
<PersonIcon />
|
||||||
<PersonIcon />
|
</IconButton>
|
||||||
</IconButton>
|
</Popover.Trigger>
|
||||||
</Popover.Trigger>
|
|
||||||
|
|
||||||
<BlitzKitTheme style={{ display: 'contents' }}>
|
<BlitzKitTheme style={{ display: "contents" }}>
|
||||||
<Popover.Content
|
<Popover.Content
|
||||||
align="end"
|
align="end"
|
||||||
width="320px"
|
width="320px"
|
||||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||||
>
|
>
|
||||||
{logins.wargaming && (
|
{logins.wargaming && (
|
||||||
<Flex direction="column" gap="2">
|
<Flex direction="column" gap="2">
|
||||||
<Text align="center" color="gray">
|
<Text align="center" color="gray">
|
||||||
Logged in accounts
|
Logged in accounts
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{logins.wargaming && (
|
{logins.wargaming && (
|
||||||
<Flex align="center" gap="2" justify="center">
|
<Flex align="center" gap="2" justify="center">
|
||||||
<WargamingIcon width={15} height={15} />
|
<WargamingIcon width={15} height={15} />
|
||||||
<Text>
|
<Text>
|
||||||
<Suspense
|
<Suspense fallback={<Skeleton height="1em" width="5rem" />}>
|
||||||
fallback={<Skeleton height="1em" width="5rem" />}
|
<WargamingAccountName />
|
||||||
>
|
</Suspense>
|
||||||
<WargamingAccountName />
|
</Text>
|
||||||
</Suspense>
|
<Button
|
||||||
</Text>
|
color="red"
|
||||||
<Button
|
variant="ghost"
|
||||||
color="red"
|
onClick={() => {
|
||||||
variant="ghost"
|
App.mutate((draft) => {
|
||||||
onClick={() => {
|
draft.logins.wargaming = undefined;
|
||||||
mutateApp((draft) => {
|
});
|
||||||
draft.logins.wargaming = undefined;
|
}}
|
||||||
});
|
>
|
||||||
}}
|
Logout
|
||||||
>
|
</Button>
|
||||||
Logout
|
</Flex>
|
||||||
</Button>
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* {!logins. !== !logins.wargaming && (
|
{/* {!logins. !== !logins.wargaming && (
|
||||||
<Inset side="x">
|
<Inset side="x">
|
||||||
<Separator my="4" size="4" />
|
<Separator my="4" size="4" />
|
||||||
</Inset>
|
</Inset>
|
||||||
)} */}
|
)} */}
|
||||||
|
|
||||||
{!logins.wargaming && (
|
{!logins.wargaming && (
|
||||||
<Flex direction="column" gap="2">
|
<Flex direction="column" gap="2">
|
||||||
<Text align="center" color="gray">
|
<Text align="center" color="gray">
|
||||||
Log in with...
|
Log in with...
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{!logins.wargaming && <WargamingLoginButton />}
|
{!logins.wargaming && <WargamingLoginButton />}
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</Popover.Content>
|
</Popover.Content>
|
||||||
</BlitzKitTheme>
|
</BlitzKitTheme>
|
||||||
</Popover.Root>
|
</Popover.Root>
|
||||||
</App.Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2,16 +2,15 @@ import {
|
|||||||
CaretDownIcon,
|
CaretDownIcon,
|
||||||
CaretSortIcon,
|
CaretSortIcon,
|
||||||
CaretUpIcon,
|
CaretUpIcon,
|
||||||
} from '@radix-ui/react-icons';
|
} from "@radix-ui/react-icons";
|
||||||
import { Button, Flex, Table } from '@radix-ui/themes';
|
import { Button, Flex, Table } from "@radix-ui/themes";
|
||||||
import { useLocale } from '../../hooks/useLocale';
|
import { useLocale } from "../../hooks/useLocale";
|
||||||
import { TankPerformanceSort } from '../../stores/tankPerformanceSort';
|
import { TankPerformanceSort } from "../../stores/tankPerformanceSort";
|
||||||
import { TankPerformanceSortTypeNamesArray } from '../../stores/tankPerformanceSort/constants';
|
import { TankPerformanceSortTypeNamesArray } from "../../stores/tankPerformanceSort/constants";
|
||||||
import { StickyColumnHeaderCell } from '../StickyColumnHeaderCell';
|
import { StickyColumnHeaderCell } from "../StickyColumnHeaderCell";
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
const sort = TankPerformanceSort.use();
|
const sort = TankPerformanceSort.use();
|
||||||
const store = TankPerformanceSort.useStore();
|
|
||||||
const { strings } = useLocale();
|
const { strings } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -34,13 +33,18 @@ export function Header() {
|
|||||||
<Flex align="center" gap="1">
|
<Flex align="center" gap="1">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
color={isSelected ? undefined : 'gray'}
|
color={isSelected ? undefined : "gray"}
|
||||||
highContrast={!isSelected}
|
highContrast={!isSelected}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
store.setState({ direction: -sort.direction as 1 | -1 });
|
TankPerformanceSort.mutate((draft) => {
|
||||||
|
draft.direction *= -1;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
store.setState({ type, direction: -1 });
|
TankPerformanceSort.mutate((draft) => {
|
||||||
|
draft.type = type;
|
||||||
|
draft.direction = -1;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,20 +1,11 @@
|
|||||||
import { assertSecret } from '@blitzkit/core';
|
import { assertSecret } from "@blitzkit/core";
|
||||||
import { ArrowRightIcon } from '@radix-ui/react-icons';
|
import { ArrowRightIcon } from "@radix-ui/react-icons";
|
||||||
import { Button, Code, Flex, Link, Text } from '@radix-ui/themes';
|
import { Button, Code, Flex, Link, Text } from "@radix-ui/themes";
|
||||||
import { App } from '../stores/app';
|
|
||||||
|
|
||||||
export function Plugs() {
|
export function Plugs() {
|
||||||
return (
|
|
||||||
<App.Provider>
|
|
||||||
<Content />
|
|
||||||
</App.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Content() {
|
|
||||||
const promoteOpentest =
|
const promoteOpentest =
|
||||||
assertSecret(import.meta.env.PUBLIC_PROMOTE_OPENTEST) === 'true' &&
|
assertSecret(import.meta.env.PUBLIC_PROMOTE_OPENTEST) === "true" &&
|
||||||
assertSecret(import.meta.env.PUBLIC_BRANCH) !== 'opentest';
|
assertSecret(import.meta.env.PUBLIC_BRANCH) !== "opentest";
|
||||||
const promotions = [promoteOpentest];
|
const promotions = [promoteOpentest];
|
||||||
|
|
||||||
if (promotions.every((promotion) => !promotion)) return null;
|
if (promotions.every((promotion) => !promotion)) return null;
|
||||||
@@ -31,11 +22,11 @@ function Content() {
|
|||||||
{promoteOpentest && (
|
{promoteOpentest && (
|
||||||
<Flex direction="column" gap="3" align="center">
|
<Flex direction="column" gap="3" align="center">
|
||||||
<Text size="5">
|
<Text size="5">
|
||||||
BlitzKit{' '}
|
BlitzKit{" "}
|
||||||
<Code variant="outline" size="4" color="gray">
|
<Code variant="outline" size="4" color="gray">
|
||||||
opentest
|
opentest
|
||||||
</Code>{' '}
|
</Code>{" "}
|
||||||
is available now{' '}
|
is available now{" "}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Link href="https://opentest.blitzkit.app/" target="_blank">
|
<Link href="https://opentest.blitzkit.app/" target="_blank">
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ import {
|
|||||||
fetchTankDefinitions,
|
fetchTankDefinitions,
|
||||||
TankType,
|
TankType,
|
||||||
type TankDefinition,
|
type TankDefinition,
|
||||||
} from '@blitzkit/core';
|
} from "@blitzkit/core";
|
||||||
import { Flex, Text, type TextProps } from '@radix-ui/themes';
|
import { Flex, Text, type TextProps } from "@radix-ui/themes";
|
||||||
import { uniq } from 'lodash-es';
|
import { uniq } from "lodash-es";
|
||||||
import { forwardRef, type ReactNode } from 'react';
|
import { forwardRef, type ReactNode } from "react";
|
||||||
import { useLocale } from '../../hooks/useLocale';
|
import { useLocale } from "../../hooks/useLocale";
|
||||||
import { TankopediaPersistent } from '../../stores/tankopediaPersistent';
|
import { TankopediaPersistent } from "../../stores/tankopediaPersistent";
|
||||||
import { classIcons } from '../ClassIcon';
|
import { classIcons } from "../ClassIcon";
|
||||||
import { LinkI18n } from '../LinkI18n';
|
import { LinkI18n } from "../LinkI18n";
|
||||||
import { MAX_RECENTLY_VIEWED } from '../TankSearch/constants';
|
import { MAX_RECENTLY_VIEWED } from "../TankSearch/constants";
|
||||||
import './index.css';
|
import "./index.css";
|
||||||
|
|
||||||
type TankCardProps = TextProps & {
|
type TankCardProps = TextProps & {
|
||||||
tank: TankDefinition;
|
tank: TankDefinition;
|
||||||
@@ -33,11 +33,11 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
|||||||
style,
|
style,
|
||||||
...props
|
...props
|
||||||
}: TankCardProps,
|
}: TankCardProps,
|
||||||
ref,
|
ref
|
||||||
) => {
|
) => {
|
||||||
const { unwrap, locale } = useLocale();
|
const { unwrap, locale } = useLocale();
|
||||||
const provideLink = !noLink && onSelect === undefined;
|
const provideLink = !noLink && onSelect === undefined;
|
||||||
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
|
||||||
const Icon = classIcons[tank.class];
|
const Icon = classIcons[tank.class];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -47,15 +47,15 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
|||||||
size="2"
|
size="2"
|
||||||
color={
|
color={
|
||||||
tank.type === TankType.COLLECTOR
|
tank.type === TankType.COLLECTOR
|
||||||
? 'blue'
|
? "blue"
|
||||||
: tank.type === TankType.PREMIUM
|
: tank.type === TankType.PREMIUM
|
||||||
? 'amber'
|
? "amber"
|
||||||
: 'gray'
|
: "gray"
|
||||||
}
|
}
|
||||||
highContrast={tank.type === TankType.RESEARCHABLE}
|
highContrast={tank.type === TankType.RESEARCHABLE}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onSelect?.(tank);
|
onSelect?.(tank);
|
||||||
mutateTankopediaPersistent((draft) => {
|
TankopediaPersistent.mutate((draft) => {
|
||||||
draft.recentlyViewed = uniq([tank.id, ...draft.recentlyViewed])
|
draft.recentlyViewed = uniq([tank.id, ...draft.recentlyViewed])
|
||||||
.filter((id) => id in tankDefinitions.tanks)
|
.filter((id) => id in tankDefinitions.tanks)
|
||||||
.slice(0, MAX_RECENTLY_VIEWED);
|
.slice(0, MAX_RECENTLY_VIEWED);
|
||||||
@@ -73,7 +73,7 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
|||||||
locale={locale}
|
locale={locale}
|
||||||
className="link"
|
className="link"
|
||||||
underline="hover"
|
underline="hover"
|
||||||
href={provideLink ? `/tanks/${tank.slug}` : '#'}
|
href={provideLink ? `/tanks/${tank.slug}` : "#"}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
if (!provideLink) event.preventDefault();
|
if (!provideLink) event.preventDefault();
|
||||||
}}
|
}}
|
||||||
@@ -104,9 +104,9 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
|||||||
color="gray"
|
color="gray"
|
||||||
align="center"
|
align="center"
|
||||||
style={{
|
style={{
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: "nowrap",
|
||||||
overflow: 'hidden',
|
overflow: "hidden",
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: "ellipsis",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{discriminator}
|
{discriminator}
|
||||||
@@ -115,5 +115,5 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
|||||||
</LinkI18n>
|
</LinkI18n>
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
@@ -12,12 +12,11 @@ import { TankCardWrapper } from "./TankCardWrapper";
|
|||||||
const tankDefinitions = await awaitableTankDefinitions;
|
const tankDefinitions = await awaitableTankDefinitions;
|
||||||
|
|
||||||
export function RecentlyViewed() {
|
export function RecentlyViewed() {
|
||||||
const tankopediaPersistentStore = TankopediaPersistent.useStore();
|
|
||||||
const filters = TankFilters.use();
|
const filters = TankFilters.use();
|
||||||
// non-reactive because it is a little weird that it updates instantly even before the page loads
|
// non-reactive because it is a little weird that it updates instantly even before the page loads
|
||||||
const recentlyViewed = tankopediaPersistentStore
|
const recentlyViewed = TankopediaPersistent.state.recentlyViewed.filter(
|
||||||
.getState()
|
(id) => id in tankDefinitions.tanks
|
||||||
.recentlyViewed.filter((id) => id in tankDefinitions.tanks);
|
);
|
||||||
const hasFilters = useMemo(
|
const hasFilters = useMemo(
|
||||||
() =>
|
() =>
|
||||||
Object.entries(filters).some(([key, value]) => {
|
Object.entries(filters).some(([key, value]) => {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const [gameDefinitions, modelDefinitions, tankDefinitions, tankNames] =
|
|||||||
export const TankSearch = memo<TankSearchProps>(
|
export const TankSearch = memo<TankSearchProps>(
|
||||||
({ compact, onSelect, onSelectAll, skeleton, ...props }) => {
|
({ compact, onSelect, onSelectAll, skeleton, ...props }) => {
|
||||||
const { strings, locale } = useLocale();
|
const { strings, locale } = useLocale();
|
||||||
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
|
||||||
const awaitedTanksDefinitionsArray = Object.values(tankDefinitions.tanks);
|
const awaitedTanksDefinitionsArray = Object.values(tankDefinitions.tanks);
|
||||||
const tankopediaSort = TankSort.use();
|
const tankopediaSort = TankSort.use();
|
||||||
const tankFilters = TankFilters.use();
|
const tankFilters = TankFilters.use();
|
||||||
@@ -443,7 +443,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
onSelectAll(tanksFiltered);
|
onSelectAll(tanksFiltered);
|
||||||
mutateTankopediaPersistent((draft) => {
|
TankopediaPersistent.mutate((draft) => {
|
||||||
draft.recentlyViewed = uniq([
|
draft.recentlyViewed = uniq([
|
||||||
...tanksFiltered.map(({ id }) => id),
|
...tanksFiltered.map(({ id }) => id),
|
||||||
...draft.recentlyViewed,
|
...draft.recentlyViewed,
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
import { assertSecret, fetchTankDefinitions } from '@blitzkit/core';
|
import { assertSecret, fetchTankDefinitions } from "@blitzkit/core";
|
||||||
import { MinusCircledIcon } from '@radix-ui/react-icons';
|
import { MinusCircledIcon } from "@radix-ui/react-icons";
|
||||||
import { Callout, Flex } from '@radix-ui/themes';
|
import { Callout, Flex } from "@radix-ui/themes";
|
||||||
import { uniq } from 'lodash-es';
|
import { uniq } from "lodash-es";
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from "react";
|
||||||
import { Duel } from '../../stores/duel';
|
import { Duel } from "../../stores/duel";
|
||||||
import { TankopediaPersistent } from '../../stores/tankopediaPersistent';
|
import { TankopediaPersistent } from "../../stores/tankopediaPersistent";
|
||||||
import { AesonPlug } from '../AesonPlug';
|
import { AesonPlug } from "../AesonPlug";
|
||||||
import { ExperimentIcon } from '../ExperimentIcon';
|
import { ExperimentIcon } from "../ExperimentIcon";
|
||||||
import { MAX_RECENTLY_VIEWED } from '../TankSearch/constants';
|
import { MAX_RECENTLY_VIEWED } from "../TankSearch/constants";
|
||||||
|
|
||||||
const tankDefinitions = await fetchTankDefinitions();
|
const tankDefinitions = await fetchTankDefinitions();
|
||||||
|
|
||||||
export function CalloutsSection() {
|
export function CalloutsSection() {
|
||||||
const tank = Duel.use((state) => state.protagonist.tank);
|
const tank = Duel.use((state) => state.protagonist.tank);
|
||||||
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
|
||||||
const promoteAeson =
|
const promoteAeson =
|
||||||
assertSecret(import.meta.env.PUBLIC_BRANCH) === 'preview';
|
assertSecret(import.meta.env.PUBLIC_BRANCH) === "preview";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
TankopediaPersistent.mutate((draft) => {
|
||||||
draft.recentlyViewed = uniq([tank.id, ...draft.recentlyViewed])
|
draft.recentlyViewed = uniq([tank.id, ...draft.recentlyViewed])
|
||||||
.filter((id) => id in tankDefinitions.tanks)
|
.filter((id) => id in tankDefinitions.tanks)
|
||||||
.slice(0, MAX_RECENTLY_VIEWED);
|
.slice(0, MAX_RECENTLY_VIEWED);
|
||||||
@@ -45,7 +44,7 @@ export function CalloutsSection() {
|
|||||||
{tank.testing && (
|
{tank.testing && (
|
||||||
<Callout.Root color="red">
|
<Callout.Root color="red">
|
||||||
<Callout.Icon>
|
<Callout.Icon>
|
||||||
<ExperimentIcon style={{ width: '1em', height: '1em' }} />
|
<ExperimentIcon style={{ width: "1em", height: "1em" }} />
|
||||||
</Callout.Icon>
|
</Callout.Icon>
|
||||||
<Callout.Text>
|
<Callout.Text>
|
||||||
Tanks in testing are subject to change and may not represent the
|
Tanks in testing are subject to change and may not represent the
|
||||||
|
|||||||
+28
-29
@@ -1,14 +1,14 @@
|
|||||||
import { DragHandleDots2Icon } from '@radix-ui/react-icons';
|
import { DragHandleDots2Icon } from "@radix-ui/react-icons";
|
||||||
import { Box, Flex, Text } from '@radix-ui/themes';
|
import { Box, Flex, Text } from "@radix-ui/themes";
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { clamp } from 'three/src/math/MathUtils.js';
|
import { clamp } from "three/src/math/MathUtils.js";
|
||||||
import {
|
import {
|
||||||
fakeLogSquishyHigh,
|
fakeLogSquishyHigh,
|
||||||
fakeLogSquishyHighInverse,
|
fakeLogSquishyHighInverse,
|
||||||
} from '../../../../../../../../../core/blitzkit/fakeLog';
|
} from "../../../../../../../../../core/blitzkit/fakeLog";
|
||||||
import { Var } from '../../../../../../../../../core/radix/var';
|
import { Var } from "../../../../../../../../../core/radix/var";
|
||||||
import { Duel } from '../../../../../../../../../stores/duel';
|
import { Duel } from "../../../../../../../../../stores/duel";
|
||||||
import './index.css';
|
import "./index.css";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
maxDistance: number;
|
maxDistance: number;
|
||||||
@@ -17,11 +17,10 @@ interface Props {
|
|||||||
|
|
||||||
export function AssaultCursor({ maxDistance, maxFakeDistance }: Props) {
|
export function AssaultCursor({ maxDistance, maxFakeDistance }: Props) {
|
||||||
const assaultDistance = Duel.use(
|
const assaultDistance = Duel.use(
|
||||||
(state) => state.protagonist.assaultDistance,
|
(state) => state.protagonist.assaultDistance
|
||||||
);
|
);
|
||||||
const [x, setX] = useState(fakeLogSquishyHigh(assaultDistance / maxDistance));
|
const [x, setX] = useState(fakeLogSquishyHigh(assaultDistance / maxDistance));
|
||||||
const cursor = useRef<HTMLDivElement>(null);
|
const cursor = useRef<HTMLDivElement>(null);
|
||||||
const mutateDuel = Duel.useMutation();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let localX = x;
|
let localX = x;
|
||||||
@@ -32,8 +31,8 @@ export function AssaultCursor({ maxDistance, maxFakeDistance }: Props) {
|
|||||||
|
|
||||||
lastX = event.clientX;
|
lastX = event.clientX;
|
||||||
|
|
||||||
window.addEventListener('pointermove', handlePointerMove);
|
window.addEventListener("pointermove", handlePointerMove);
|
||||||
window.addEventListener('pointerup', handlePointerUp);
|
window.addEventListener("pointerup", handlePointerUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePointerMove(event: PointerEvent) {
|
function handlePointerMove(event: PointerEvent) {
|
||||||
@@ -54,21 +53,21 @@ export function AssaultCursor({ maxDistance, maxFakeDistance }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handlePointerUp() {
|
function handlePointerUp() {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.protagonist.assaultDistance =
|
draft.protagonist.assaultDistance =
|
||||||
fakeLogSquishyHighInverse(localX * maxFakeDistance) * maxDistance;
|
fakeLogSquishyHighInverse(localX * maxFakeDistance) * maxDistance;
|
||||||
});
|
});
|
||||||
|
|
||||||
window.removeEventListener('pointermove', handlePointerMove);
|
window.removeEventListener("pointermove", handlePointerMove);
|
||||||
window.removeEventListener('pointerup', handlePointerUp);
|
window.removeEventListener("pointerup", handlePointerUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor.current?.addEventListener('pointerdown', handlePointerDown);
|
cursor.current?.addEventListener("pointerdown", handlePointerDown);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
cursor.current?.removeEventListener('pointerdown', handlePointerDown);
|
cursor.current?.removeEventListener("pointerdown", handlePointerDown);
|
||||||
window.removeEventListener('pointermove', handlePointerMove);
|
window.removeEventListener("pointermove", handlePointerMove);
|
||||||
window.removeEventListener('pointerup', handlePointerUp);
|
window.removeEventListener("pointerup", handlePointerUp);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -82,9 +81,9 @@ export function AssaultCursor({ maxDistance, maxFakeDistance }: Props) {
|
|||||||
width="2pt"
|
width="2pt"
|
||||||
id="assault-cursor"
|
id="assault-cursor"
|
||||||
style={{
|
style={{
|
||||||
touchAction: 'none',
|
touchAction: "none",
|
||||||
transform: 'translateX(-50%)',
|
transform: "translateX(-50%)",
|
||||||
backgroundColor: Var('accent-11'),
|
backgroundColor: Var("accent-11"),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
@@ -93,19 +92,19 @@ export function AssaultCursor({ maxDistance, maxFakeDistance }: Props) {
|
|||||||
position="absolute"
|
position="absolute"
|
||||||
left="50%"
|
left="50%"
|
||||||
bottom="100%"
|
bottom="100%"
|
||||||
style={{ transform: 'translateX(-50%)' }}
|
style={{ transform: "translateX(-50%)" }}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
align="center"
|
align="center"
|
||||||
justify="center"
|
justify="center"
|
||||||
style={{
|
style={{
|
||||||
width: '1.5rem',
|
width: "1.5rem",
|
||||||
height: '1.5rem',
|
height: "1.5rem",
|
||||||
backgroundColor: Var('accent-9'),
|
backgroundColor: Var("accent-9"),
|
||||||
borderRadius: `50% 50% ${Var('radius-1')} ${Var('radius-1')}`,
|
borderRadius: `50% 50% ${Var("radius-1")} ${Var("radius-1")}`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text style={{ display: 'flex' }} highContrast color="purple">
|
<Text style={{ display: "flex" }} highContrast color="purple">
|
||||||
<DragHandleDots2Icon />
|
<DragHandleDots2Icon />
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -114,7 +113,7 @@ export function AssaultCursor({ maxDistance, maxFakeDistance }: Props) {
|
|||||||
height="0.5rem"
|
height="0.5rem"
|
||||||
width="2pt"
|
width="2pt"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: Var('accent-11'),
|
backgroundColor: Var("accent-11"),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
+26
-27
@@ -1,24 +1,23 @@
|
|||||||
import { asset, isExplosive } from '@blitzkit/core';
|
import { asset, isExplosive } from "@blitzkit/core";
|
||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { Flex, Heading, IconButton, Tooltip } from '@radix-ui/themes';
|
import { Flex, Heading, IconButton, Tooltip } from "@radix-ui/themes";
|
||||||
import { useLocale } from '../../../../../../hooks/useLocale';
|
import { useLocale } from "../../../../../../hooks/useLocale";
|
||||||
import { useTankModelDefinition } from '../../../../../../hooks/useTankModelDefinition';
|
import { useTankModelDefinition } from "../../../../../../hooks/useTankModelDefinition";
|
||||||
import { Duel } from '../../../../../../stores/duel';
|
import { Duel } from "../../../../../../stores/duel";
|
||||||
import type { MaybeSkeletonComponentProps } from '../../../../../../types/maybeSkeletonComponentProps';
|
import type { MaybeSkeletonComponentProps } from "../../../../../../types/maybeSkeletonComponentProps";
|
||||||
import { AssaultRangesVisualizer } from './AssaultRangesVisualizer';
|
import { AssaultRangesVisualizer } from "./AssaultRangesVisualizer";
|
||||||
import { GunFlexibilityVisualizer } from './GunFlexibilityVisualizer';
|
import { GunFlexibilityVisualizer } from "./GunFlexibilityVisualizer";
|
||||||
import { Info } from './Info';
|
import { Info } from "./Info";
|
||||||
import { InfoWithDelta } from './InfoWithDelta';
|
import { InfoWithDelta } from "./InfoWithDelta";
|
||||||
import { ReloadVisualizer } from './ReloadVisualizer';
|
import { ReloadVisualizer } from "./ReloadVisualizer";
|
||||||
import { RicochetVisualizer } from './RicochetVisualizer';
|
import { RicochetVisualizer } from "./RicochetVisualizer";
|
||||||
import { StatsTableWrapper } from './StatsTableWrapper';
|
import { StatsTableWrapper } from "./StatsTableWrapper";
|
||||||
import type { StatsAcceptorProps } from './TraverseVisualizer';
|
import type { StatsAcceptorProps } from "./TraverseVisualizer";
|
||||||
|
|
||||||
export function Firepower({
|
export function Firepower({
|
||||||
stats,
|
stats,
|
||||||
skeleton,
|
skeleton,
|
||||||
}: StatsAcceptorProps & MaybeSkeletonComponentProps) {
|
}: StatsAcceptorProps & MaybeSkeletonComponentProps) {
|
||||||
const mutateDuel = Duel.useMutation();
|
|
||||||
const { strings, unwrap } = useLocale();
|
const { strings, unwrap } = useLocale();
|
||||||
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);
|
||||||
@@ -41,8 +40,8 @@ export function Firepower({
|
|||||||
return (
|
return (
|
||||||
<Tooltip content={unwrap(thisShell.name)} key={thisShell.id}>
|
<Tooltip content={unwrap(thisShell.name)} key={thisShell.id}>
|
||||||
<IconButton
|
<IconButton
|
||||||
color={selected ? undefined : 'gray'}
|
color={selected ? undefined : "gray"}
|
||||||
variant={selected ? 'solid' : 'soft'}
|
variant={selected ? "solid" : "soft"}
|
||||||
highContrast={selected}
|
highContrast={selected}
|
||||||
style={{
|
style={{
|
||||||
borderTopLeftRadius: shellIndex === 0 ? undefined : 0,
|
borderTopLeftRadius: shellIndex === 0 ? undefined : 0,
|
||||||
@@ -54,7 +53,7 @@ export function Firepower({
|
|||||||
marginLeft: shellIndex === 0 ? 0 : -1,
|
marginLeft: shellIndex === 0 ? 0 : -1,
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.protagonist.shell = thisShell;
|
draft.protagonist.shell = thisShell;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -79,7 +78,7 @@ export function Firepower({
|
|||||||
</Info>
|
</Info>
|
||||||
<InfoWithDelta stats={stats} decimals={0} value="dpm" />
|
<InfoWithDelta stats={stats} decimals={0} value="dpm" />
|
||||||
<InfoWithDelta stats={stats} decimals={0} value="damage" />
|
<InfoWithDelta stats={stats} decimals={0} value="damage" />
|
||||||
{gun.gun_type!.$case !== 'regular' && (
|
{gun.gun_type!.$case !== "regular" && (
|
||||||
<InfoWithDelta stats={stats} indent decimals={0} value="clipDamage" />
|
<InfoWithDelta stats={stats} indent decimals={0} value="clipDamage" />
|
||||||
)}
|
)}
|
||||||
<InfoWithDelta stats={stats} decimals={0} value="moduleDamage" />
|
<InfoWithDelta stats={stats} decimals={0} value="moduleDamage" />
|
||||||
@@ -89,13 +88,13 @@ export function Firepower({
|
|||||||
<AssaultRangesVisualizer stats={stats} ranges={gun.assault_ranges} />
|
<AssaultRangesVisualizer stats={stats} ranges={gun.assault_ranges} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{gun.gun_type!.$case === 'auto_reloader' && (
|
{gun.gun_type!.$case === "auto_reloader" && (
|
||||||
<InfoWithDelta stats={stats} decimals={0} indent value="dpmEffective" />
|
<InfoWithDelta stats={stats} decimals={0} indent value="dpmEffective" />
|
||||||
)}
|
)}
|
||||||
{gun.gun_type!.$case !== 'regular' && (
|
{gun.gun_type!.$case !== "regular" && (
|
||||||
<InfoWithDelta stats={stats} value="shells" />
|
<InfoWithDelta stats={stats} value="shells" />
|
||||||
)}
|
)}
|
||||||
{gun.gun_type!.$case === 'auto_reloader' ? (
|
{gun.gun_type!.$case === "auto_reloader" ? (
|
||||||
<>
|
<>
|
||||||
<Info
|
<Info
|
||||||
indent
|
indent
|
||||||
@@ -120,7 +119,7 @@ export function Firepower({
|
|||||||
name={literals(
|
name={literals(
|
||||||
strings.website.tools.tankopedia.characteristics.values
|
strings.website.tools.tankopedia.characteristics.values
|
||||||
.shell_index,
|
.shell_index,
|
||||||
[`${index + 1}`],
|
[`${index + 1}`]
|
||||||
)}
|
)}
|
||||||
decimals={2}
|
decimals={2}
|
||||||
deltaType="lowerIsBetter"
|
deltaType="lowerIsBetter"
|
||||||
@@ -137,8 +136,8 @@ export function Firepower({
|
|||||||
value="shellReload"
|
value="shellReload"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(gun.gun_type!.$case === 'auto_loader' ||
|
{(gun.gun_type!.$case === "auto_loader" ||
|
||||||
gun.gun_type!.$case === 'auto_reloader') && (
|
gun.gun_type!.$case === "auto_reloader") && (
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
stats={stats}
|
stats={stats}
|
||||||
indent
|
indent
|
||||||
@@ -192,7 +191,7 @@ export function Firepower({
|
|||||||
name={literals(
|
name={literals(
|
||||||
strings.website.tools.tankopedia.characteristics.values
|
strings.website.tools.tankopedia.characteristics.values
|
||||||
.dispersion_at_distance,
|
.dispersion_at_distance,
|
||||||
['100'],
|
["100"]
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
|
|||||||
+43
-48
@@ -1,22 +1,22 @@
|
|||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { Box, Card, Flex } from '@radix-ui/themes';
|
import { Box, Card, Flex } from "@radix-ui/themes";
|
||||||
import { clamp } from 'lodash-es';
|
import { clamp } from "lodash-es";
|
||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from "react";
|
||||||
import { degToRad, radToDeg } from 'three/src/math/MathUtils.js';
|
import { degToRad, radToDeg } from "three/src/math/MathUtils.js";
|
||||||
import { awaitableModelDefinitions } from '../../../../../../../core/awaitables/modelDefinitions';
|
import { awaitableModelDefinitions } from "../../../../../../../core/awaitables/modelDefinitions";
|
||||||
import {
|
import {
|
||||||
DEFAULT_PITCH_TRANSITION,
|
DEFAULT_PITCH_TRANSITION,
|
||||||
applyPitchYawLimits,
|
applyPitchYawLimits,
|
||||||
} from '../../../../../../../core/blitz/applyPitchYawLimits';
|
} from "../../../../../../../core/blitz/applyPitchYawLimits";
|
||||||
import { hasEquipment } from '../../../../../../../core/blitzkit/hasEquipment';
|
import { hasEquipment } from "../../../../../../../core/blitzkit/hasEquipment";
|
||||||
import { modelTransformEvent } from '../../../../../../../core/blitzkit/modelTransform';
|
import { modelTransformEvent } from "../../../../../../../core/blitzkit/modelTransform";
|
||||||
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 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";
|
||||||
import { FlexibilityCanvas } from './components/FlexibilityCanvas';
|
import { FlexibilityCanvas } from "./components/FlexibilityCanvas";
|
||||||
|
|
||||||
const ANGLE_COEFFICIENT = 1 / 10;
|
const ANGLE_COEFFICIENT = 1 / 10;
|
||||||
|
|
||||||
@@ -40,7 +40,6 @@ export function GunFlexibilityVisualizer({
|
|||||||
}: MaybeSkeletonComponentProps) {
|
}: MaybeSkeletonComponentProps) {
|
||||||
const { strings } = useLocale();
|
const { strings } = useLocale();
|
||||||
|
|
||||||
const duelStore = Duel.useStore();
|
|
||||||
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);
|
||||||
@@ -52,10 +51,10 @@ export function GunFlexibilityVisualizer({
|
|||||||
const [yaw, setYaw] = useState(0);
|
const [yaw, setYaw] = useState(0);
|
||||||
const [pitch, setPitch] = useState(0);
|
const [pitch, setPitch] = useState(0);
|
||||||
const [minPitch, setMinPitch] = useState(
|
const [minPitch, setMinPitch] = useState(
|
||||||
-degToRad(gunModel.pitch.front?.max ?? gunModel.pitch.max),
|
-degToRad(gunModel.pitch.front?.max ?? gunModel.pitch.max)
|
||||||
);
|
);
|
||||||
const [maxPitch, setMaxPitch] = useState(
|
const [maxPitch, setMaxPitch] = useState(
|
||||||
-degToRad(gunModel.pitch.front?.min ?? gunModel.pitch.min),
|
-degToRad(gunModel.pitch.front?.min ?? gunModel.pitch.min)
|
||||||
);
|
);
|
||||||
|
|
||||||
const container = useRef<HTMLDivElement>(null);
|
const container = useRef<HTMLDivElement>(null);
|
||||||
@@ -68,7 +67,7 @@ export function GunFlexibilityVisualizer({
|
|||||||
const b = p.back;
|
const b = p.back;
|
||||||
const t = gunModel.pitch.transition ?? DEFAULT_PITCH_TRANSITION;
|
const t = gunModel.pitch.transition ?? DEFAULT_PITCH_TRANSITION;
|
||||||
|
|
||||||
let d = '';
|
let d = "";
|
||||||
|
|
||||||
// right
|
// right
|
||||||
const mo = mag(p.max);
|
const mo = mag(p.max);
|
||||||
@@ -169,10 +168,10 @@ export function GunFlexibilityVisualizer({
|
|||||||
return (
|
return (
|
||||||
<Flex direction="column-reverse" gap="0">
|
<Flex direction="column-reverse" gap="0">
|
||||||
<Box mt="-3" px="2">
|
<Box mt="-3" px="2">
|
||||||
<Card variant="classic" style={{ height: '10rem' }}>
|
<Card variant="classic" style={{ height: "10rem" }}>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
background: 'linear-gradient(var(--gray-1), var(--gray-3))',
|
background: "linear-gradient(var(--gray-1), var(--gray-3))",
|
||||||
}}
|
}}
|
||||||
position="absolute"
|
position="absolute"
|
||||||
width="100%"
|
width="100%"
|
||||||
@@ -195,8 +194,8 @@ export function GunFlexibilityVisualizer({
|
|||||||
transparent,
|
transparent,
|
||||||
transparent ${Math.PI / 2 - maxPitch}rad,
|
transparent ${Math.PI / 2 - maxPitch}rad,
|
||||||
|
|
||||||
${Var('cyan-a4')} ${Math.PI / 2 - maxPitch}rad,
|
${Var("cyan-a4")} ${Math.PI / 2 - maxPitch}rad,
|
||||||
${Var('jade-a4')} ${Math.PI / 2 - minPitch}rad,
|
${Var("jade-a4")} ${Math.PI / 2 - minPitch}rad,
|
||||||
|
|
||||||
transparent ${Math.PI / 2 - minPitch}rad,
|
transparent ${Math.PI / 2 - minPitch}rad,
|
||||||
transparent 180deg
|
transparent 180deg
|
||||||
@@ -209,7 +208,7 @@ export function GunFlexibilityVisualizer({
|
|||||||
|
|
||||||
<VisualizerCard
|
<VisualizerCard
|
||||||
ref={container}
|
ref={container}
|
||||||
style={{ touchAction: 'none' }}
|
style={{ touchAction: "none" }}
|
||||||
onPointerMove={(event) => {
|
onPointerMove={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
@@ -221,19 +220,15 @@ export function GunFlexibilityVisualizer({
|
|||||||
const v = event.clientY - rect.top - rect.height / 2;
|
const v = event.clientY - rect.top - rect.height / 2;
|
||||||
let yaw = Math.atan2(u, -v);
|
let yaw = Math.atan2(u, -v);
|
||||||
|
|
||||||
const {
|
|
||||||
equipmentMatrix,
|
|
||||||
tank: { equipment_preset },
|
|
||||||
} = duelStore.getState().protagonist;
|
|
||||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||||
122,
|
122,
|
||||||
equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
equipmentMatrix,
|
Duel.state.protagonist.equipmentMatrix
|
||||||
);
|
);
|
||||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||||
124,
|
124,
|
||||||
equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
equipmentMatrix,
|
Duel.state.protagonist.equipmentMatrix
|
||||||
);
|
);
|
||||||
|
|
||||||
const min = applyPitchYawLimits(
|
const min = applyPitchYawLimits(
|
||||||
@@ -242,7 +237,7 @@ export function GunFlexibilityVisualizer({
|
|||||||
p,
|
p,
|
||||||
y,
|
y,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
hasDownImprovedVerticalStabilizer,
|
hasDownImprovedVerticalStabilizer
|
||||||
);
|
);
|
||||||
const max = applyPitchYawLimits(
|
const max = applyPitchYawLimits(
|
||||||
Math.PI,
|
Math.PI,
|
||||||
@@ -250,7 +245,7 @@ export function GunFlexibilityVisualizer({
|
|||||||
p,
|
p,
|
||||||
y,
|
y,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
hasDownImprovedVerticalStabilizer,
|
hasDownImprovedVerticalStabilizer
|
||||||
);
|
);
|
||||||
|
|
||||||
yaw = min[1];
|
yaw = min[1];
|
||||||
@@ -276,8 +271,8 @@ export function GunFlexibilityVisualizer({
|
|||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
style={{
|
style={{
|
||||||
borderRadius: Var('radius-2'),
|
borderRadius: Var("radius-2"),
|
||||||
overflow: 'hidden',
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
@@ -286,18 +281,18 @@ export function GunFlexibilityVisualizer({
|
|||||||
style={{
|
style={{
|
||||||
opacity: 2 ** -3,
|
opacity: 2 ** -3,
|
||||||
backgroundImage: `url(/assets/images/tankopedia/visualizers/ricochet/armor-hash.png)`,
|
backgroundImage: `url(/assets/images/tankopedia/visualizers/ricochet/armor-hash.png)`,
|
||||||
boxShadow: 'inset 0 0 4rem 1rem black',
|
boxShadow: "inset 0 0 4rem 1rem black",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
width: 'calc(100%)',
|
width: "calc(100%)",
|
||||||
height: 'calc(100%)',
|
height: "calc(100%)",
|
||||||
}}
|
}}
|
||||||
viewBox="-1 -1 2 2"
|
viewBox="-1 -1 2 2"
|
||||||
>
|
>
|
||||||
@@ -340,11 +335,11 @@ export function GunFlexibilityVisualizer({
|
|||||||
width="1pt"
|
width="1pt"
|
||||||
height="14rem"
|
height="14rem"
|
||||||
style={{
|
style={{
|
||||||
borderRadius: Var('radius-1'),
|
borderRadius: Var("radius-1"),
|
||||||
background: `linear-gradient(${Var('gray-a11')}, ${Var('gray-a6')})`,
|
background: `linear-gradient(${Var("gray-a11")}, ${Var("gray-a6")})`,
|
||||||
boxShadow:
|
boxShadow:
|
||||||
'var(--white-a12) -0.25rem 0 1rem, var(--black-a12) 0.25rem 0 1rem',
|
"var(--white-a12) -0.25rem 0 1rem, var(--black-a12) 0.25rem 0 1rem",
|
||||||
transform: 'translateX(-50%)',
|
transform: "translateX(-50%)",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -356,9 +351,9 @@ export function GunFlexibilityVisualizer({
|
|||||||
width="0.25rem"
|
width="0.25rem"
|
||||||
height="0.25rem"
|
height="0.25rem"
|
||||||
style={{
|
style={{
|
||||||
borderRadius: '50%',
|
borderRadius: "50%",
|
||||||
backgroundColor: Var('gray-12'),
|
backgroundColor: Var("gray-12"),
|
||||||
transform: 'translate(-50%, 50%)',
|
transform: "translate(-50%, 50%)",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
+11
-11
@@ -1,12 +1,12 @@
|
|||||||
import { asset } from '@blitzkit/core';
|
import { asset } from "@blitzkit/core";
|
||||||
import { Flex, IconButton, Tooltip } from '@radix-ui/themes';
|
import { Flex, IconButton, Tooltip } from "@radix-ui/themes";
|
||||||
import { useLocale } from '../../../../../../../../hooks/useLocale';
|
import { useLocale } from "../../../../../../../../hooks/useLocale";
|
||||||
import { Duel } from '../../../../../../../../stores/duel';
|
import { Duel } from "../../../../../../../../stores/duel";
|
||||||
|
|
||||||
export function ShellSwitcher() {
|
export function ShellSwitcher() {
|
||||||
const gun = Duel.use((state) => state.protagonist.gun);
|
const gun = Duel.use((state) => state.protagonist.gun);
|
||||||
const shell = Duel.use((state) => state.protagonist.shell);
|
const shell = Duel.use((state) => state.protagonist.shell);
|
||||||
const mutateDuel = Duel.useMutation();
|
|
||||||
const { unwrap } = useLocale();
|
const { unwrap } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -18,26 +18,26 @@ export function ShellSwitcher() {
|
|||||||
<Tooltip content={unwrap(thisShell.name)} key={thisShell.id}>
|
<Tooltip content={unwrap(thisShell.name)} key={thisShell.id}>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="3"
|
size="3"
|
||||||
color={selected ? undefined : 'gray'}
|
color={selected ? undefined : "gray"}
|
||||||
variant={selected ? 'solid' : 'soft'}
|
variant={selected ? "solid" : "soft"}
|
||||||
highContrast={selected}
|
highContrast={selected}
|
||||||
style={{
|
style={{
|
||||||
borderTopLeftRadius: 0,
|
borderTopLeftRadius: 0,
|
||||||
borderBottomLeftRadius: shellIndex === 0 ? '50%' : 0,
|
borderBottomLeftRadius: shellIndex === 0 ? "50%" : 0,
|
||||||
borderTopRightRadius: 0,
|
borderTopRightRadius: 0,
|
||||||
borderBottomRightRadius:
|
borderBottomRightRadius:
|
||||||
shellIndex === gun.shells.length - 1 ? '50%' : 0,
|
shellIndex === gun.shells.length - 1 ? "50%" : 0,
|
||||||
marginLeft: shellIndex === 0 ? 0 : -1,
|
marginLeft: shellIndex === 0 ? 0 : -1,
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.protagonist.shell = thisShell;
|
draft.protagonist.shell = thisShell;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={unwrap(thisShell.name)}
|
alt={unwrap(thisShell.name)}
|
||||||
style={{ width: '1.125rem', height: '1.125rem' }}
|
style={{ width: "1.125rem", height: "1.125rem" }}
|
||||||
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|||||||
+16
-17
@@ -1,5 +1,5 @@
|
|||||||
import { checkConsumableProvisionInclusivity } from '@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity';
|
import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity";
|
||||||
import { InfoCircledIcon } from '@radix-ui/react-icons';
|
import { InfoCircledIcon } from "@radix-ui/react-icons";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Flex,
|
Flex,
|
||||||
@@ -7,31 +7,30 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
Popover,
|
Popover,
|
||||||
Text,
|
Text,
|
||||||
} from '@radix-ui/themes';
|
} from "@radix-ui/themes";
|
||||||
import { awaitableConsumableDefinitions } from '../../../../core/awaitables/consumableDefinitions';
|
import { awaitableConsumableDefinitions } from "../../../../core/awaitables/consumableDefinitions";
|
||||||
import { useEquipment } from '../../../../hooks/useEquipment';
|
import { useEquipment } from "../../../../hooks/useEquipment";
|
||||||
import { useLocale } from '../../../../hooks/useLocale';
|
import { useLocale } from "../../../../hooks/useLocale";
|
||||||
import { Duel } from '../../../../stores/duel';
|
import { Duel } from "../../../../stores/duel";
|
||||||
import { ConsumablesManager } from '../../../ConsumablesManager';
|
import { ConsumablesManager } from "../../../ConsumablesManager";
|
||||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper";
|
||||||
|
|
||||||
const consumableDefinitions = await awaitableConsumableDefinitions;
|
const consumableDefinitions = await awaitableConsumableDefinitions;
|
||||||
|
|
||||||
export function Consumables() {
|
export function Consumables() {
|
||||||
const mutateDuel = Duel.useMutation();
|
|
||||||
const protagonist = Duel.use((state) => state.protagonist);
|
const protagonist = Duel.use((state) => state.protagonist);
|
||||||
const consumables = Duel.use((state) => state.protagonist.consumables);
|
const consumables = Duel.use((state) => state.protagonist.consumables);
|
||||||
const consumablesList = Object.values(
|
const consumablesList = Object.values(
|
||||||
consumableDefinitions.consumables,
|
consumableDefinitions.consumables
|
||||||
).filter((consumable) =>
|
).filter((consumable) =>
|
||||||
checkConsumableProvisionInclusivity(
|
checkConsumableProvisionInclusivity(
|
||||||
consumable,
|
consumable,
|
||||||
protagonist.tank,
|
protagonist.tank,
|
||||||
protagonist.gun,
|
protagonist.gun
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
const cooldownBooster = Duel.use(
|
const cooldownBooster = Duel.use(
|
||||||
(state) => state.protagonist.cooldownBooster,
|
(state) => state.protagonist.cooldownBooster
|
||||||
);
|
);
|
||||||
const hasConsumableDeliverySystem = useEquipment(118);
|
const hasConsumableDeliverySystem = useEquipment(118);
|
||||||
const hasHighEndConsumables = useEquipment(101);
|
const hasHighEndConsumables = useEquipment(101);
|
||||||
@@ -67,7 +66,7 @@ export function Consumables() {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
color="red"
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.protagonist.consumables = [];
|
draft.protagonist.consumables = [];
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -85,11 +84,11 @@ export function Consumables() {
|
|||||||
hasHighEndConsumables={hasHighEndConsumables}
|
hasHighEndConsumables={hasHighEndConsumables}
|
||||||
timers
|
timers
|
||||||
onConsumablesChange={(consumables) => {
|
onConsumablesChange={(consumables) => {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.protagonist.consumables = consumables;
|
draft.protagonist.consumables = consumables;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
justify={{ initial: 'center', sm: 'start' }}
|
justify={{ initial: "center", sm: "start" }}
|
||||||
/>
|
/>
|
||||||
</ConfigurationChildWrapper>
|
</ConfigurationChildWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
+9
-10
@@ -1,19 +1,18 @@
|
|||||||
import { Button, Flex, Heading } from '@radix-ui/themes';
|
import { Button, Flex, Heading } from "@radix-ui/themes";
|
||||||
import { awaitableEquipmentDefinitions } from '../../../../core/awaitables/equipmentDefinitions';
|
import { awaitableEquipmentDefinitions } from "../../../../core/awaitables/equipmentDefinitions";
|
||||||
import { useLocale } from '../../../../hooks/useLocale';
|
import { useLocale } from "../../../../hooks/useLocale";
|
||||||
import { Duel } from '../../../../stores/duel';
|
import { Duel } from "../../../../stores/duel";
|
||||||
import { EquipmentManager } from '../../../EquipmentManager';
|
import { EquipmentManager } from "../../../EquipmentManager";
|
||||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper";
|
||||||
|
|
||||||
const equipmentDefinitions = await awaitableEquipmentDefinitions;
|
const equipmentDefinitions = await awaitableEquipmentDefinitions;
|
||||||
|
|
||||||
export function Equipment() {
|
export function Equipment() {
|
||||||
const mutateDuel = Duel.useMutation();
|
|
||||||
const protagonist = Duel.use((state) => state.protagonist);
|
const protagonist = Duel.use((state) => state.protagonist);
|
||||||
const equipmentPreset =
|
const equipmentPreset =
|
||||||
equipmentDefinitions.presets[protagonist.tank.equipment_preset];
|
equipmentDefinitions.presets[protagonist.tank.equipment_preset];
|
||||||
const equipmentMatrix = Duel.use(
|
const equipmentMatrix = Duel.use(
|
||||||
(state) => state.protagonist.equipmentMatrix,
|
(state) => state.protagonist.equipmentMatrix
|
||||||
);
|
);
|
||||||
const { strings } = useLocale();
|
const { strings } = useLocale();
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ export function Equipment() {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
color="red"
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.protagonist.equipmentMatrix.forEach((row) => {
|
draft.protagonist.equipmentMatrix.forEach((row) => {
|
||||||
row.forEach((_, index) => {
|
row.forEach((_, index) => {
|
||||||
row[index] = 0;
|
row[index] = 0;
|
||||||
@@ -44,7 +43,7 @@ export function Equipment() {
|
|||||||
matrix={equipmentMatrix}
|
matrix={equipmentMatrix}
|
||||||
preset={equipmentPreset}
|
preset={equipmentPreset}
|
||||||
onChange={(matrix) => {
|
onChange={(matrix) => {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.protagonist.equipmentMatrix = matrix;
|
draft.protagonist.equipmentMatrix = matrix;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
+18
-19
@@ -1,15 +1,14 @@
|
|||||||
import { asset } from '@blitzkit/core';
|
import { asset } from "@blitzkit/core";
|
||||||
import { Flex, Heading } from '@radix-ui/themes';
|
import { 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 { GenericTankComponentButton } from '../../../ModuleButtons/GenericTankComponentButton';
|
import { GenericTankComponentButton } from "../../../ModuleButtons/GenericTankComponentButton";
|
||||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper";
|
||||||
|
|
||||||
export function Miscellaneous() {
|
export function Miscellaneous() {
|
||||||
const mutateDuel = Duel.useMutation();
|
|
||||||
const camouflage = Duel.use((state) => state.protagonist.camouflage);
|
const camouflage = Duel.use((state) => state.protagonist.camouflage);
|
||||||
const cooldownBooster = Duel.use(
|
const cooldownBooster = Duel.use(
|
||||||
(state) => state.protagonist.cooldownBooster,
|
(state) => state.protagonist.cooldownBooster
|
||||||
);
|
);
|
||||||
const { tank } = Duel.use((state) => state.protagonist);
|
const { tank } = Duel.use((state) => state.protagonist);
|
||||||
const { strings } = useLocale();
|
const { strings } = useLocale();
|
||||||
@@ -23,37 +22,37 @@ export function Miscellaneous() {
|
|||||||
<Flex gap="2" align="center">
|
<Flex gap="2" align="center">
|
||||||
{!tank.fixed_camouflage && (
|
{!tank.fixed_camouflage && (
|
||||||
<GenericTankComponentButton
|
<GenericTankComponentButton
|
||||||
icon={asset('icons/camo.webp')}
|
icon={asset("icons/camo.webp")}
|
||||||
selected={camouflage}
|
selected={camouflage}
|
||||||
iconStyles={{
|
iconStyles={{
|
||||||
top: '50%',
|
top: "50%",
|
||||||
left: '50%',
|
left: "50%",
|
||||||
transform: 'translate(calc(-50% + 4px), calc(-50% + 4px))',
|
transform: "translate(calc(-50% + 4px), calc(-50% + 4px))",
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.protagonist.camouflage = !camouflage;
|
draft.protagonist.camouflage = !camouflage;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<GenericTankComponentButton
|
<GenericTankComponentButton
|
||||||
icon={asset('icons/boosters/equipment.webp')}
|
icon={asset("icons/boosters/equipment.webp")}
|
||||||
selected={cooldownBooster > 0}
|
selected={cooldownBooster > 0}
|
||||||
banner={
|
banner={
|
||||||
cooldownBooster === 0
|
cooldownBooster === 0
|
||||||
? undefined
|
? undefined
|
||||||
: cooldownBooster === 1
|
: cooldownBooster === 1
|
||||||
? '#afb0abc0'
|
? "#afb0abc0"
|
||||||
: cooldownBooster === 2
|
: cooldownBooster === 2
|
||||||
? '#5f72cbc0'
|
? "#5f72cbc0"
|
||||||
: '#9b3cc0c0'
|
: "#9b3cc0c0"
|
||||||
}
|
}
|
||||||
iconStyles={{
|
iconStyles={{
|
||||||
transform: 'translate(-50%, -50%) scale(0.8)',
|
transform: "translate(-50%, -50%) scale(0.8)",
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.protagonist.cooldownBooster++;
|
draft.protagonist.cooldownBooster++;
|
||||||
if (draft.protagonist.cooldownBooster === 4)
|
if (draft.protagonist.cooldownBooster === 4)
|
||||||
draft.protagonist.cooldownBooster = 0;
|
draft.protagonist.cooldownBooster = 0;
|
||||||
|
|||||||
+44
-45
@@ -10,7 +10,7 @@ import {
|
|||||||
TurretDefinition,
|
TurretDefinition,
|
||||||
type TankDefinition,
|
type TankDefinition,
|
||||||
type Unlock,
|
type Unlock,
|
||||||
} from '@blitzkit/core';
|
} from "@blitzkit/core";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
@@ -18,12 +18,12 @@ import {
|
|||||||
Heading,
|
Heading,
|
||||||
IconButton,
|
IconButton,
|
||||||
Text,
|
Text,
|
||||||
} from '@radix-ui/themes';
|
} from "@radix-ui/themes";
|
||||||
import { awaitableTankDefinitions } from '../../../../core/awaitables/tankDefinitions';
|
import { awaitableTankDefinitions } from "../../../../core/awaitables/tankDefinitions";
|
||||||
import { useLocale } from '../../../../hooks/useLocale';
|
import { useLocale } from "../../../../hooks/useLocale";
|
||||||
import { Duel } from '../../../../stores/duel';
|
import { Duel } from "../../../../stores/duel";
|
||||||
import { LinkI18n } from '../../../LinkI18n';
|
import { LinkI18n } from "../../../LinkI18n";
|
||||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper";
|
||||||
|
|
||||||
type ModuleDefinition =
|
type ModuleDefinition =
|
||||||
| TurretDefinition
|
| TurretDefinition
|
||||||
@@ -32,7 +32,7 @@ type ModuleDefinition =
|
|||||||
| EngineDefinition;
|
| EngineDefinition;
|
||||||
|
|
||||||
function extractModule(raw: ModuleDefinition | TankDefinition) {
|
function extractModule(raw: ModuleDefinition | TankDefinition) {
|
||||||
if ('gun_type' in raw) {
|
if ("gun_type" in raw) {
|
||||||
return raw;
|
return raw;
|
||||||
} else
|
} else
|
||||||
return raw as Exclude<ModuleDefinition | TankDefinition, GunDefinition>;
|
return raw as Exclude<ModuleDefinition | TankDefinition, GunDefinition>;
|
||||||
@@ -43,11 +43,11 @@ interface ModuleWithUnlocks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const moduleTypeTypeIconName: Record<ModuleType, string> = {
|
const moduleTypeTypeIconName: Record<ModuleType, string> = {
|
||||||
[ModuleType.VEHICLE]: '',
|
[ModuleType.VEHICLE]: "",
|
||||||
[ModuleType.ENGINE]: 'engine',
|
[ModuleType.ENGINE]: "engine",
|
||||||
[ModuleType.TRACKS]: 'chassis',
|
[ModuleType.TRACKS]: "chassis",
|
||||||
[ModuleType.TURRET]: 'turret',
|
[ModuleType.TURRET]: "turret",
|
||||||
[ModuleType.GUN]: 'gun',
|
[ModuleType.GUN]: "gun",
|
||||||
};
|
};
|
||||||
|
|
||||||
function ModuleButton({
|
function ModuleButton({
|
||||||
@@ -70,11 +70,11 @@ function ModuleButton({
|
|||||||
<IconButton
|
<IconButton
|
||||||
size="4"
|
size="4"
|
||||||
radius="small"
|
radius="small"
|
||||||
variant={selected ? 'surface' : 'soft'}
|
variant={selected ? "surface" : "soft"}
|
||||||
color={top ? 'amber' : selected ? undefined : 'gray'}
|
color={top ? "amber" : selected ? undefined : "gray"}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
style={{
|
style={{
|
||||||
position: 'relative',
|
position: "relative",
|
||||||
width: isTank ? 72 : undefined,
|
width: isTank ? 72 : undefined,
|
||||||
height: isTank ? 72 : undefined,
|
height: isTank ? 72 : undefined,
|
||||||
}}
|
}}
|
||||||
@@ -83,7 +83,7 @@ function ModuleButton({
|
|||||||
size="1"
|
size="1"
|
||||||
color="gray"
|
color="gray"
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
left: isTank ? 8 : 4,
|
left: isTank ? 8 : 4,
|
||||||
top: isTank ? 8 : 4,
|
top: isTank ? 8 : 4,
|
||||||
}}
|
}}
|
||||||
@@ -94,13 +94,13 @@ function ModuleButton({
|
|||||||
{unlock.cost.value > 0 && (
|
{unlock.cost.value > 0 && (
|
||||||
<Flex
|
<Flex
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
left: '50%',
|
left: "50%",
|
||||||
bottom: 4,
|
bottom: 4,
|
||||||
transform: 'translateX(-50%)',
|
transform: "translateX(-50%)",
|
||||||
}}
|
}}
|
||||||
align="center"
|
align="center"
|
||||||
gap={isTank ? '1' : undefined}
|
gap={isTank ? "1" : undefined}
|
||||||
>
|
>
|
||||||
<Text size="1" color="gray">
|
<Text size="1" color="gray">
|
||||||
{formatCompact(locale, unlock.cost.value)}
|
{formatCompact(locale, unlock.cost.value)}
|
||||||
@@ -112,7 +112,7 @@ function ModuleButton({
|
|||||||
style={{
|
style={{
|
||||||
width: 12,
|
width: 12,
|
||||||
height: 12,
|
height: 12,
|
||||||
objectFit: 'contain',
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -128,7 +128,7 @@ function ModuleButton({
|
|||||||
style={{
|
style={{
|
||||||
width: isTank ? 64 : 32,
|
width: isTank ? 64 : 32,
|
||||||
height: isTank ? 64 : 32,
|
height: isTank ? 64 : 32,
|
||||||
objectFit: 'contain',
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -149,7 +149,6 @@ function ModuleButton({
|
|||||||
const tankDefinitions = await awaitableTankDefinitions;
|
const tankDefinitions = await awaitableTankDefinitions;
|
||||||
|
|
||||||
export function Modules() {
|
export function Modules() {
|
||||||
const mutateDuel = Duel.useMutation();
|
|
||||||
const tank = Duel.use((state) => state.protagonist.tank);
|
const tank = Duel.use((state) => state.protagonist.tank);
|
||||||
const hasUpgrades =
|
const hasUpgrades =
|
||||||
tank.turrets.length > 1 ||
|
tank.turrets.length > 1 ||
|
||||||
@@ -171,15 +170,15 @@ export function Modules() {
|
|||||||
const { strings } = useLocale();
|
const { strings } = useLocale();
|
||||||
|
|
||||||
function setByUnlock(unlock: Unlock) {
|
function setByUnlock(unlock: Unlock) {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
if (unlock.type === ModuleType.TURRET) {
|
if (unlock.type === ModuleType.TURRET) {
|
||||||
draft.protagonist.turret = draft.protagonist.tank.turrets.find(
|
draft.protagonist.turret = draft.protagonist.tank.turrets.find(
|
||||||
(turret) => turret.id === unlock.id,
|
(turret) => turret.id === unlock.id
|
||||||
)!;
|
)!;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!draft.protagonist.turret.guns.some(
|
!draft.protagonist.turret.guns.some(
|
||||||
(gun) => gun.id === draft.protagonist.gun.id,
|
(gun) => gun.id === draft.protagonist.gun.id
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
draft.protagonist.gun = draft.protagonist.turret.guns.at(-1)!;
|
draft.protagonist.gun = draft.protagonist.turret.guns.at(-1)!;
|
||||||
@@ -187,7 +186,7 @@ export function Modules() {
|
|||||||
}
|
}
|
||||||
} else if (unlock.type === ModuleType.GUN) {
|
} else if (unlock.type === ModuleType.GUN) {
|
||||||
const gunInTurret = draft.protagonist.turret.guns.find(
|
const gunInTurret = draft.protagonist.turret.guns.find(
|
||||||
(gun) => gun.id === unlock.id,
|
(gun) => gun.id === unlock.id
|
||||||
);
|
);
|
||||||
if (gunInTurret) {
|
if (gunInTurret) {
|
||||||
draft.protagonist.gun = gunInTurret;
|
draft.protagonist.gun = gunInTurret;
|
||||||
@@ -195,10 +194,10 @@ export function Modules() {
|
|||||||
} else {
|
} else {
|
||||||
// TODO: warn somehow?
|
// TODO: warn somehow?
|
||||||
const suitableTurret = draft.protagonist.tank.turrets.find((turret) =>
|
const suitableTurret = draft.protagonist.tank.turrets.find((turret) =>
|
||||||
turret.guns.some((gun) => gun.id === unlock.id),
|
turret.guns.some((gun) => gun.id === unlock.id)
|
||||||
)!;
|
)!;
|
||||||
const gunInSuitableTurret = suitableTurret.guns.find(
|
const gunInSuitableTurret = suitableTurret.guns.find(
|
||||||
(gun) => gun.id === unlock.id,
|
(gun) => gun.id === unlock.id
|
||||||
)!;
|
)!;
|
||||||
|
|
||||||
draft.protagonist.turret = suitableTurret;
|
draft.protagonist.turret = suitableTurret;
|
||||||
@@ -207,11 +206,11 @@ export function Modules() {
|
|||||||
}
|
}
|
||||||
} else if (unlock.type === ModuleType.ENGINE) {
|
} else if (unlock.type === ModuleType.ENGINE) {
|
||||||
draft.protagonist.engine = draft.protagonist.tank.engines.find(
|
draft.protagonist.engine = draft.protagonist.tank.engines.find(
|
||||||
(engine) => engine.id === unlock.id,
|
(engine) => engine.id === unlock.id
|
||||||
)!;
|
)!;
|
||||||
} else if (unlock.type === ModuleType.TRACKS) {
|
} else if (unlock.type === ModuleType.TRACKS) {
|
||||||
draft.protagonist.track = draft.protagonist.tank.tracks.find(
|
draft.protagonist.track = draft.protagonist.tank.tracks.find(
|
||||||
(track) => track.id === unlock.id,
|
(track) => track.id === unlock.id
|
||||||
)!;
|
)!;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -235,7 +234,7 @@ export function Modules() {
|
|||||||
} else if (unlock.type === ModuleType.GUN) {
|
} else if (unlock.type === ModuleType.GUN) {
|
||||||
module = tank.turrets
|
module = tank.turrets
|
||||||
.find((turret) =>
|
.find((turret) =>
|
||||||
turret.guns.some((gun) => gun.id === unlock.id),
|
turret.guns.some((gun) => gun.id === unlock.id)
|
||||||
)!
|
)!
|
||||||
.guns.find((gun) => gun.id === unlock.id)!;
|
.guns.find((gun) => gun.id === unlock.id)!;
|
||||||
} else if (unlock.type === ModuleType.VEHICLE) {
|
} else if (unlock.type === ModuleType.VEHICLE) {
|
||||||
@@ -257,12 +256,12 @@ export function Modules() {
|
|||||||
{unlocks.length > 1 && (
|
{unlocks.length > 1 && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'currentcolor',
|
backgroundColor: "currentcolor",
|
||||||
width: `calc(${central ? 100 : 50}% + ${
|
width: `calc(${central ? 100 : 50}% + ${
|
||||||
central ? 8 : 4
|
central ? 8 : 4
|
||||||
}px)`,
|
}px)`,
|
||||||
height: 1,
|
height: 1,
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
right: first ? 0 : undefined,
|
right: first ? 0 : undefined,
|
||||||
left: last ? 0 : undefined,
|
left: last ? 0 : undefined,
|
||||||
transform: `translateX(${first ? 4 : last ? -4 : 0}px)`,
|
transform: `translateX(${first ? 4 : last ? -4 : 0}px)`,
|
||||||
@@ -279,14 +278,14 @@ export function Modules() {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'currentcolor',
|
backgroundColor: "currentcolor",
|
||||||
width: 1,
|
width: 1,
|
||||||
height: 8,
|
height: 8,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon
|
||||||
style={{
|
style={{
|
||||||
transform: 'translateY(-7px)',
|
transform: "translateY(-7px)",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -326,7 +325,7 @@ export function Modules() {
|
|||||||
1 && (
|
1 && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'currentcolor',
|
backgroundColor: "currentcolor",
|
||||||
width: 1,
|
width: 1,
|
||||||
height: 8,
|
height: 8,
|
||||||
marginBottom: -8,
|
marginBottom: -8,
|
||||||
@@ -337,7 +336,7 @@ export function Modules() {
|
|||||||
{(extractModule(module) as ModuleWithUnlocks).unlocks &&
|
{(extractModule(module) as ModuleWithUnlocks).unlocks &&
|
||||||
tree(
|
tree(
|
||||||
type,
|
type,
|
||||||
(extractModule(module) as ModuleWithUnlocks).unlocks!,
|
(extractModule(module) as ModuleWithUnlocks).unlocks!
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
@@ -359,7 +358,7 @@ export function Modules() {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
color="red"
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.protagonist.turret = draft.protagonist.tank.turrets[0];
|
draft.protagonist.turret = draft.protagonist.tank.turrets[0];
|
||||||
draft.protagonist.gun = draft.protagonist.turret.guns[0];
|
draft.protagonist.gun = draft.protagonist.turret.guns[0];
|
||||||
draft.protagonist.shell = draft.protagonist.gun.shells[0];
|
draft.protagonist.shell = draft.protagonist.gun.shells[0];
|
||||||
@@ -373,7 +372,7 @@ export function Modules() {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.protagonist.turret =
|
draft.protagonist.turret =
|
||||||
draft.protagonist.tank.turrets.at(-1)!;
|
draft.protagonist.tank.turrets.at(-1)!;
|
||||||
draft.protagonist.gun = draft.protagonist.turret.guns.at(-1)!;
|
draft.protagonist.gun = draft.protagonist.turret.guns.at(-1)!;
|
||||||
@@ -394,28 +393,28 @@ export function Modules() {
|
|||||||
<Flex gap="2" wrap="wrap" gapY="6">
|
<Flex gap="2" wrap="wrap" gapY="6">
|
||||||
{tree(ModuleType.ENGINE, [
|
{tree(ModuleType.ENGINE, [
|
||||||
{
|
{
|
||||||
cost: { type: 'xp', value: 0 },
|
cost: { type: "xp", value: 0 },
|
||||||
id: engine0.id,
|
id: engine0.id,
|
||||||
type: ModuleType.ENGINE,
|
type: ModuleType.ENGINE,
|
||||||
},
|
},
|
||||||
])}
|
])}
|
||||||
{tree(ModuleType.TRACKS, [
|
{tree(ModuleType.TRACKS, [
|
||||||
{
|
{
|
||||||
cost: { type: 'xp', value: 0 },
|
cost: { type: "xp", value: 0 },
|
||||||
id: track0.id,
|
id: track0.id,
|
||||||
type: ModuleType.TRACKS,
|
type: ModuleType.TRACKS,
|
||||||
},
|
},
|
||||||
])}
|
])}
|
||||||
{tree(ModuleType.TURRET, [
|
{tree(ModuleType.TURRET, [
|
||||||
{
|
{
|
||||||
cost: { type: 'xp', value: 0 },
|
cost: { type: "xp", value: 0 },
|
||||||
id: turret0.id,
|
id: turret0.id,
|
||||||
type: ModuleType.TURRET,
|
type: ModuleType.TURRET,
|
||||||
},
|
},
|
||||||
])}
|
])}
|
||||||
{tree(ModuleType.GUN, [
|
{tree(ModuleType.GUN, [
|
||||||
{
|
{
|
||||||
cost: { type: 'xp', value: 0 },
|
cost: { type: "xp", value: 0 },
|
||||||
id: gun0.id,
|
id: gun0.id,
|
||||||
type: ModuleType.GUN,
|
type: ModuleType.GUN,
|
||||||
},
|
},
|
||||||
|
|||||||
+9
-10
@@ -1,15 +1,14 @@
|
|||||||
import { availableProvisions } from '@blitzkit/core';
|
import { availableProvisions } from "@blitzkit/core";
|
||||||
import { Button, Flex, Heading } from '@radix-ui/themes';
|
import { Button, Flex, Heading } from "@radix-ui/themes";
|
||||||
import { awaitableProvisionDefinitions } from '../../../../core/awaitables/provisionDefinitions';
|
import { awaitableProvisionDefinitions } from "../../../../core/awaitables/provisionDefinitions";
|
||||||
import { useLocale } from '../../../../hooks/useLocale';
|
import { useLocale } from "../../../../hooks/useLocale";
|
||||||
import { Duel } from '../../../../stores/duel';
|
import { Duel } from "../../../../stores/duel";
|
||||||
import { ProvisionsManager } from '../../../ProvisionsManager';
|
import { ProvisionsManager } from "../../../ProvisionsManager";
|
||||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper";
|
||||||
|
|
||||||
const provisionDefinitions = await awaitableProvisionDefinitions;
|
const provisionDefinitions = await awaitableProvisionDefinitions;
|
||||||
|
|
||||||
export function Provisions() {
|
export function Provisions() {
|
||||||
const mutateDuel = Duel.useMutation();
|
|
||||||
const { tank, gun } = Duel.use((state) => state.protagonist);
|
const { tank, gun } = Duel.use((state) => state.protagonist);
|
||||||
const provisions = Duel.use((state) => state.protagonist.provisions);
|
const provisions = Duel.use((state) => state.protagonist.provisions);
|
||||||
const provisionsList = availableProvisions(tank, gun, provisionDefinitions);
|
const provisionsList = availableProvisions(tank, gun, provisionDefinitions);
|
||||||
@@ -25,7 +24,7 @@ export function Provisions() {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
color="red"
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.protagonist.provisions = [];
|
draft.protagonist.provisions = [];
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -39,7 +38,7 @@ export function Provisions() {
|
|||||||
selected={provisions}
|
selected={provisions}
|
||||||
disabled={tank.max_provisions === provisions.length}
|
disabled={tank.max_provisions === provisions.length}
|
||||||
onChange={(provisions) => {
|
onChange={(provisions) => {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.protagonist.provisions = provisions;
|
draft.protagonist.provisions = provisions;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
+1
-2
@@ -6,7 +6,6 @@ import { Tankopedia } from "../../../../../../stores/tankopedia";
|
|||||||
|
|
||||||
export function DynamicArmorSwitcher() {
|
export function DynamicArmorSwitcher() {
|
||||||
const tank = Duel.use((state) => state.protagonist.tank);
|
const tank = Duel.use((state) => state.protagonist.tank);
|
||||||
const mutateDuel = Duel.useMutation();
|
|
||||||
const isDynamicArmorActive = Duel.use((state) =>
|
const isDynamicArmorActive = Duel.use((state) =>
|
||||||
state.protagonist.consumables.includes(73)
|
state.protagonist.consumables.includes(73)
|
||||||
);
|
);
|
||||||
@@ -20,7 +19,7 @@ export function DynamicArmorSwitcher() {
|
|||||||
variant="soft"
|
variant="soft"
|
||||||
size={{ initial: "2", sm: "3" }}
|
size={{ initial: "2", sm: "3" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
if (draft.protagonist.consumables.includes(73)) {
|
if (draft.protagonist.consumables.includes(73)) {
|
||||||
draft.protagonist.consumables =
|
draft.protagonist.consumables =
|
||||||
draft.protagonist.consumables.filter((id) => id !== 73);
|
draft.protagonist.consumables.filter((id) => id !== 73);
|
||||||
|
|||||||
+3
-4
@@ -25,7 +25,6 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
|
|||||||
const showPrimaryArmor = TankopediaPersistent.use(
|
const showPrimaryArmor = TankopediaPersistent.use(
|
||||||
(state) => state.showPrimaryArmor
|
(state) => state.showPrimaryArmor
|
||||||
);
|
);
|
||||||
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
|
||||||
const editStatic = Tankopedia.use((state) => state.editStatic);
|
const editStatic = Tankopedia.use((state) => state.editStatic);
|
||||||
const { strings } = useLocale();
|
const { strings } = useLocale();
|
||||||
|
|
||||||
@@ -50,7 +49,7 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
|
|||||||
gap="2"
|
gap="2"
|
||||||
style={{ opacity: showPrimaryArmor ? 1 : 0.5, cursor: "pointer" }}
|
style={{ opacity: showPrimaryArmor ? 1 : 0.5, cursor: "pointer" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
TankopediaPersistent.mutate((draft) => {
|
||||||
draft.showPrimaryArmor = !draft.showPrimaryArmor;
|
draft.showPrimaryArmor = !draft.showPrimaryArmor;
|
||||||
});
|
});
|
||||||
Tankopedia.mutate((draft) => {
|
Tankopedia.mutate((draft) => {
|
||||||
@@ -91,7 +90,7 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
|
|||||||
gap="2"
|
gap="2"
|
||||||
style={{ opacity: showSpacedArmor ? 1 : 0.5, cursor: "pointer" }}
|
style={{ opacity: showSpacedArmor ? 1 : 0.5, cursor: "pointer" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
TankopediaPersistent.mutate((draft) => {
|
||||||
draft.showSpacedArmor = !draft.showSpacedArmor;
|
draft.showSpacedArmor = !draft.showSpacedArmor;
|
||||||
});
|
});
|
||||||
Tankopedia.mutate((draft) => {
|
Tankopedia.mutate((draft) => {
|
||||||
@@ -130,7 +129,7 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
|
|||||||
gap="2"
|
gap="2"
|
||||||
style={{ opacity: showExternalModules ? 1 : 0.5, cursor: "pointer" }}
|
style={{ opacity: showExternalModules ? 1 : 0.5, cursor: "pointer" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
TankopediaPersistent.mutate((draft) => {
|
||||||
draft.showExternalModules = !draft.showExternalModules;
|
draft.showExternalModules = !draft.showExternalModules;
|
||||||
});
|
});
|
||||||
Tankopedia.mutate((draft) => {
|
Tankopedia.mutate((draft) => {
|
||||||
|
|||||||
@@ -69,10 +69,8 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
const antagonistTank = Duel.use((state) => state.antagonist.tank);
|
const antagonistTank = Duel.use((state) => state.antagonist.tank);
|
||||||
const hasCalibratedShells = useEquipment(103, true);
|
const hasCalibratedShells = useEquipment(103, true);
|
||||||
const [tab, setTab] = useState("search");
|
const [tab, setTab] = useState("search");
|
||||||
const mutateDuel = Duel.useMutation();
|
|
||||||
const hasEnhancedArmor = useEquipment(110);
|
const hasEnhancedArmor = useEquipment(110);
|
||||||
const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets);
|
const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets);
|
||||||
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
|
||||||
const { strings, unwrap } = useLocale();
|
const { strings, unwrap } = useLocale();
|
||||||
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);
|
||||||
@@ -151,7 +149,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
module="gun"
|
module="gun"
|
||||||
key={gun.id}
|
key={gun.id}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.antagonist.turret = turret;
|
draft.antagonist.turret = turret;
|
||||||
draft.antagonist.gun = gun;
|
draft.antagonist.gun = gun;
|
||||||
draft.antagonist.shell = gun.shells[0];
|
draft.antagonist.shell = gun.shells[0];
|
||||||
@@ -192,7 +190,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
size={{ initial: "2", sm: "3" }}
|
size={{ initial: "2", sm: "3" }}
|
||||||
radius="none"
|
radius="none"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.antagonist.shell = thisShell;
|
draft.antagonist.shell = thisShell;
|
||||||
});
|
});
|
||||||
Tankopedia.mutate((draft) => {
|
Tankopedia.mutate((draft) => {
|
||||||
@@ -229,7 +227,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
size={{ initial: "2", sm: "3" }}
|
size={{ initial: "2", sm: "3" }}
|
||||||
radius="none"
|
radius="none"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.antagonist.equipmentMatrix[0][0] = hasCalibratedShells
|
draft.antagonist.equipmentMatrix[0][0] = hasCalibratedShells
|
||||||
? 0
|
? 0
|
||||||
: 1;
|
: 1;
|
||||||
@@ -254,7 +252,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
size={{ initial: "2", sm: "3" }}
|
size={{ initial: "2", sm: "3" }}
|
||||||
radius="none"
|
radius="none"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.protagonist.equipmentMatrix[1][1] = hasEnhancedArmor
|
draft.protagonist.equipmentMatrix[1][1] = hasEnhancedArmor
|
||||||
? 0
|
? 0
|
||||||
: -1;
|
: -1;
|
||||||
@@ -303,7 +301,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
mb="1"
|
mb="1"
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
TankopediaPersistent.mutate((draft) => {
|
||||||
draft.advancedHighlighting = !draft.advancedHighlighting;
|
draft.advancedHighlighting = !draft.advancedHighlighting;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -343,7 +341,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
<TankSearch
|
<TankSearch
|
||||||
compact
|
compact
|
||||||
onSelect={(tank) => {
|
onSelect={(tank) => {
|
||||||
mutateDuel((draft) => {
|
Duel.mutate((draft) => {
|
||||||
draft.antagonist.tank = tank;
|
draft.antagonist.tank = tank;
|
||||||
draft.antagonist.engine = tank.engines.at(-1)!;
|
draft.antagonist.engine = tank.engines.at(-1)!;
|
||||||
draft.antagonist.track = tank.tracks.at(-1)!;
|
draft.antagonist.track = tank.tracks.at(-1)!;
|
||||||
|
|||||||
+4
-6
@@ -32,7 +32,6 @@ export function Controls({
|
|||||||
zoomable = true,
|
zoomable = true,
|
||||||
enableRotate = true,
|
enableRotate = true,
|
||||||
}: ControlsProps) {
|
}: ControlsProps) {
|
||||||
const duelStore = Duel.useStore();
|
|
||||||
const camera = useThree((state) => state.camera as PerspectiveCamera);
|
const camera = useThree((state) => state.camera as PerspectiveCamera);
|
||||||
const canvas = useThree((state) => state.gl.domElement);
|
const canvas = useThree((state) => state.gl.domElement);
|
||||||
const orbitControls = useRef<OrbitControlsClass>(null);
|
const orbitControls = useRef<OrbitControlsClass>(null);
|
||||||
@@ -108,16 +107,15 @@ export function Controls({
|
|||||||
);
|
);
|
||||||
|
|
||||||
function handlePoseEvent(event: Pose) {
|
function handlePoseEvent(event: Pose) {
|
||||||
const duel = duelStore.getState();
|
|
||||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||||
122,
|
122,
|
||||||
duel.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
duel.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix
|
||||||
);
|
);
|
||||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||||
124,
|
124,
|
||||||
duel.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
duel.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix
|
||||||
);
|
);
|
||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
|
|||||||
+8
-11
@@ -14,7 +14,6 @@ import { TankopediaDisplay } from "../../../../../../stores/tankopediaPersistent
|
|||||||
import { ModelTankWrapper } from "../../../../../Armor/components/ModelTankWrapper";
|
import { ModelTankWrapper } from "../../../../../Armor/components/ModelTankWrapper";
|
||||||
|
|
||||||
export function TankModel() {
|
export function TankModel() {
|
||||||
const duelStore = Duel.useStore();
|
|
||||||
const protagonist = Duel.use((draft) => draft.protagonist);
|
const protagonist = Duel.use((draft) => draft.protagonist);
|
||||||
const track = Duel.use((state) => state.protagonist.track);
|
const track = Duel.use((state) => state.protagonist.track);
|
||||||
const turret = Duel.use((state) => state.protagonist.turret);
|
const turret = Duel.use((state) => state.protagonist.turret);
|
||||||
@@ -135,16 +134,15 @@ export function TankModel() {
|
|||||||
window.addEventListener("pointerup", handlePointerUp);
|
window.addEventListener("pointerup", handlePointerUp);
|
||||||
}
|
}
|
||||||
function handlePointerMove(event: PointerEvent) {
|
function handlePointerMove(event: PointerEvent) {
|
||||||
const duel = duelStore.getState();
|
|
||||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||||
122,
|
122,
|
||||||
duel.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
duel.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix
|
||||||
);
|
);
|
||||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||||
124,
|
124,
|
||||||
duel.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
duel.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix
|
||||||
);
|
);
|
||||||
const boundingRect = canvas.getBoundingClientRect();
|
const boundingRect = canvas.getBoundingClientRect();
|
||||||
|
|
||||||
@@ -223,16 +221,15 @@ export function TankModel() {
|
|||||||
window.addEventListener("pointerup", handlePointerUp);
|
window.addEventListener("pointerup", handlePointerUp);
|
||||||
}
|
}
|
||||||
function handlePointerMove(event: PointerEvent) {
|
function handlePointerMove(event: PointerEvent) {
|
||||||
const duel = duelStore.getState();
|
|
||||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||||
122,
|
122,
|
||||||
duel.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
duel.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix
|
||||||
);
|
);
|
||||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||||
124,
|
124,
|
||||||
duel.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
duel.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix
|
||||||
);
|
);
|
||||||
const boundingRect = canvas.getBoundingClientRect();
|
const boundingRect = canvas.getBoundingClientRect();
|
||||||
delta.set(event.clientX, event.clientY).sub(position);
|
delta.set(event.clientX, event.clientY).sub(position);
|
||||||
|
|||||||
@@ -36,8 +36,6 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
|
|
||||||
return { value } satisfies ThicknessRange;
|
return { value } satisfies ThicknessRange;
|
||||||
}, [protagonist]);
|
}, [protagonist]);
|
||||||
const duelStore = Duel.useStore();
|
|
||||||
const mutateDuel = Duel.useMutation();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disturbed) {
|
if (disturbed) {
|
||||||
@@ -47,12 +45,13 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleKeyDown(event: KeyboardEvent) {
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
const { shells } = duelStore.getState().antagonist.gun;
|
|
||||||
|
|
||||||
times(3, (index) => {
|
times(3, (index) => {
|
||||||
if (event.key === `${index + 1}` && shells.length > index) {
|
if (
|
||||||
mutateDuel((draft) => {
|
event.key === `${index + 1}` &&
|
||||||
draft.antagonist.shell = shells[index];
|
Duel.state.antagonist.gun.shells.length > index
|
||||||
|
) {
|
||||||
|
Duel.mutate((draft) => {
|
||||||
|
draft.antagonist.shell = Duel.state.antagonist.gun.shells[index];
|
||||||
});
|
});
|
||||||
Tankopedia.mutate((draft) => {
|
Tankopedia.mutate((draft) => {
|
||||||
draft.customShell = undefined;
|
draft.customShell = undefined;
|
||||||
|
|||||||
+26
-27
@@ -1,5 +1,5 @@
|
|||||||
import type { BlitzkitResponse, StarsInt } from '@blitzkit/core';
|
import type { BlitzkitResponse, StarsInt } from "@blitzkit/core";
|
||||||
import { PaperPlaneIcon } from '@radix-ui/react-icons';
|
import { PaperPlaneIcon } from "@radix-ui/react-icons";
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
Button,
|
Button,
|
||||||
@@ -7,15 +7,14 @@ import {
|
|||||||
Flex,
|
Flex,
|
||||||
Spinner,
|
Spinner,
|
||||||
Text,
|
Text,
|
||||||
} from '@radix-ui/themes';
|
} from "@radix-ui/themes";
|
||||||
import { useState } from 'react';
|
import { useState } from "react";
|
||||||
import { useLocale } from '../../../../../../hooks/useLocale';
|
import { useLocale } from "../../../../../../hooks/useLocale";
|
||||||
import { App } from '../../../../../../stores/app';
|
import { App } from "../../../../../../stores/app";
|
||||||
import { Duel } from '../../../../../../stores/duel';
|
import { Duel } from "../../../../../../stores/duel";
|
||||||
import { Stars } from '../../Stars';
|
import { Stars } from "../../Stars";
|
||||||
|
|
||||||
export function VoteCaster() {
|
export function VoteCaster() {
|
||||||
const appStore = App.useStore();
|
|
||||||
const tank = Duel.use((state) => state.protagonist.tank);
|
const tank = Duel.use((state) => state.protagonist.tank);
|
||||||
const [easiness, setEasiness] = useState<StarsInt | null>(null);
|
const [easiness, setEasiness] = useState<StarsInt | null>(null);
|
||||||
const [firepower, setFirepower] = useState<StarsInt | null>(null);
|
const [firepower, setFirepower] = useState<StarsInt | null>(null);
|
||||||
@@ -42,28 +41,28 @@ export function VoteCaster() {
|
|||||||
<Flex direction="column" mt="4">
|
<Flex direction="column" mt="4">
|
||||||
<Flex align="center" justify="between">
|
<Flex align="center" justify="between">
|
||||||
<Text>
|
<Text>
|
||||||
Easiness{' '}
|
Easiness{" "}
|
||||||
<Text color="gray" size="2">
|
<Text color="gray" size="2">
|
||||||
{easiness === null ? '(empty)' : `(${easiness})`}
|
{easiness === null ? "(empty)" : `(${easiness})`}
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
<Stars stars={easiness} size="5" onCast={setEasiness} />
|
<Stars stars={easiness} size="5" onCast={setEasiness} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex align="center" justify="between">
|
<Flex align="center" justify="between">
|
||||||
<Text>
|
<Text>
|
||||||
Firepower{' '}
|
Firepower{" "}
|
||||||
<Text color="gray" size="2">
|
<Text color="gray" size="2">
|
||||||
{firepower === null ? '(empty)' : `(${firepower})`}
|
{firepower === null ? "(empty)" : `(${firepower})`}
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
<Stars stars={firepower} size="5" onCast={setFirepower} />
|
<Stars stars={firepower} size="5" onCast={setFirepower} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex align="center" justify="between">
|
<Flex align="center" justify="between">
|
||||||
<Text>
|
<Text>
|
||||||
Maneuverability{' '}
|
Maneuverability{" "}
|
||||||
<Text color="gray" size="2">
|
<Text color="gray" size="2">
|
||||||
{maneuverability === null
|
{maneuverability === null
|
||||||
? '(empty)'
|
? "(empty)"
|
||||||
: `(${maneuverability})`}
|
: `(${maneuverability})`}
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
@@ -75,9 +74,9 @@ export function VoteCaster() {
|
|||||||
</Flex>
|
</Flex>
|
||||||
<Flex align="center" justify="between">
|
<Flex align="center" justify="between">
|
||||||
<Text>
|
<Text>
|
||||||
Survivability{' '}
|
Survivability{" "}
|
||||||
<Text color="gray" size="2">
|
<Text color="gray" size="2">
|
||||||
{survivability === null ? '(empty)' : `(${survivability})`}
|
{survivability === null ? "(empty)" : `(${survivability})`}
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
<Stars stars={survivability} size="5" onCast={setSurvivability} />
|
<Stars stars={survivability} size="5" onCast={setSurvivability} />
|
||||||
@@ -100,7 +99,7 @@ export function VoteCaster() {
|
|||||||
survivability === null)
|
survivability === null)
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const { wargaming } = appStore.getState().logins;
|
const { wargaming } = App.state.logins;
|
||||||
|
|
||||||
if (!wargaming) {
|
if (!wargaming) {
|
||||||
return setShowDialogue(false);
|
return setShowDialogue(false);
|
||||||
@@ -110,26 +109,26 @@ export function VoteCaster() {
|
|||||||
|
|
||||||
fetch(
|
fetch(
|
||||||
`/api/tank-voting/${tank.id}/cast?${
|
`/api/tank-voting/${tank.id}/cast?${
|
||||||
easiness === null ? '' : `easiness=${easiness}&`
|
easiness === null ? "" : `easiness=${easiness}&`
|
||||||
}${firepower === null ? '' : `firepower=${firepower}&`}${
|
}${firepower === null ? "" : `firepower=${firepower}&`}${
|
||||||
maneuverability === null
|
maneuverability === null
|
||||||
? ''
|
? ""
|
||||||
: `maneuverability=${maneuverability}&`
|
: `maneuverability=${maneuverability}&`
|
||||||
}${
|
}${
|
||||||
survivability === null
|
survivability === null
|
||||||
? ''
|
? ""
|
||||||
: `survivability=${survivability}&`
|
: `survivability=${survivability}&`
|
||||||
}player=${wargaming.id}&token=${wargaming.token}`,
|
}player=${wargaming.id}&token=${wargaming.token}`
|
||||||
)
|
)
|
||||||
.then(
|
.then(
|
||||||
(response) => response.json() as Promise<BlitzkitResponse>,
|
(response) => response.json() as Promise<BlitzkitResponse>
|
||||||
)
|
)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
setShowDialogue(false);
|
setShowDialogue(false);
|
||||||
setPostVote(json.status === 'ok');
|
setPostVote(json.status === "ok");
|
||||||
|
|
||||||
if (json.status === 'error') {
|
if (json.status === "error") {
|
||||||
console.error('vote failed', json);
|
console.error("vote failed", json);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
import { type Draft, produce } from 'immer';
|
|
||||||
import {
|
|
||||||
createContext,
|
|
||||||
type ReactNode,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { type StoreApi, useStore as useStoreZustand } from 'zustand';
|
|
||||||
|
|
||||||
type StoreProviderProps<InitData> = {
|
|
||||||
children: ReactNode;
|
|
||||||
} & (InitData extends unknown ? { data?: InitData } : { data: InitData });
|
|
||||||
|
|
||||||
// idk why zustand doesn't export this
|
|
||||||
type ExtractState<S> = S extends {
|
|
||||||
getState: () => infer T;
|
|
||||||
}
|
|
||||||
? T
|
|
||||||
: never;
|
|
||||||
|
|
||||||
export function createContextualStore<
|
|
||||||
Store extends StoreApi<unknown>,
|
|
||||||
InitData,
|
|
||||||
>(initialize: (data: InitData) => Store) {
|
|
||||||
type Type = Store extends StoreApi<infer T> ? T : never;
|
|
||||||
|
|
||||||
const Context = createContext<Store | undefined>(undefined);
|
|
||||||
|
|
||||||
function Provider(props: StoreProviderProps<InitData>) {
|
|
||||||
const storeRef = useRef<Store>();
|
|
||||||
if (!storeRef.current) {
|
|
||||||
storeRef.current = initialize((props as { data: InitData }).data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Context.Provider value={storeRef.current}>
|
|
||||||
{props.children}
|
|
||||||
</Context.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function useStore() {
|
|
||||||
const store = useContext(Context);
|
|
||||||
if (!store) throw new Error('useStore must be used within Provider');
|
|
||||||
return store as Store;
|
|
||||||
}
|
|
||||||
|
|
||||||
function use<Slice = Type>(
|
|
||||||
selector: (store: Type) => Slice = (state) => state as Slice,
|
|
||||||
) {
|
|
||||||
const store = useStore();
|
|
||||||
return useStoreZustand(
|
|
||||||
store,
|
|
||||||
selector as (state: ExtractState<Store>) => Slice,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function useDeferred<Slice = Type>(
|
|
||||||
selector: (store: Type) => Slice = (state) => state as Slice,
|
|
||||||
initial: Slice,
|
|
||||||
) {
|
|
||||||
const [state, setState] = useState(initial);
|
|
||||||
const trueState = use(selector);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setState(trueState);
|
|
||||||
}, [trueState]);
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
function useMutation() {
|
|
||||||
const counterStoreContext = useStore();
|
|
||||||
|
|
||||||
return function (recipe: (draft: Draft<Type>) => void) {
|
|
||||||
counterStoreContext.setState(
|
|
||||||
produce<Type>((draft) => {
|
|
||||||
recipe(draft);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { Provider, use, useMutation, useStore, useDeferred };
|
|
||||||
}
|
|
||||||
@@ -1,48 +1,38 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from "react";
|
||||||
import { App } from '../../../../stores/app';
|
import { App } from "../../../../stores/app";
|
||||||
|
|
||||||
export const AUTH_PROVIDERS = ['wargaming'] as const;
|
export const AUTH_PROVIDERS = ["wargaming"] as const;
|
||||||
|
|
||||||
export type AuthProvider = (typeof AUTH_PROVIDERS)[number];
|
export type AuthProvider = (typeof AUTH_PROVIDERS)[number];
|
||||||
|
|
||||||
export const AUTH_PROVIDER_NAMES: Record<AuthProvider, string> = {
|
export const AUTH_PROVIDER_NAMES: Record<AuthProvider, string> = {
|
||||||
wargaming: 'Wargaming',
|
wargaming: "Wargaming",
|
||||||
};
|
};
|
||||||
|
|
||||||
interface AuthorizeProps {
|
interface AuthorizeProps {
|
||||||
provider: AuthProvider;
|
provider: AuthProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Authorize(props: AuthorizeProps) {
|
export function Authorize({ provider }: AuthorizeProps) {
|
||||||
return (
|
|
||||||
<App.Provider>
|
|
||||||
<Content {...props} />
|
|
||||||
</App.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Content({ provider }: AuthorizeProps) {
|
|
||||||
const mutateApp = App.useMutation();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const searchParams = new URLSearchParams(window.location.search);
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
switch (provider) {
|
switch (provider) {
|
||||||
case 'wargaming': {
|
case "wargaming": {
|
||||||
if (
|
if (
|
||||||
!searchParams.has('expires_at') ||
|
!searchParams.has("expires_at") ||
|
||||||
!searchParams.has('access_token') ||
|
!searchParams.has("access_token") ||
|
||||||
!searchParams.has('account_id')
|
!searchParams.has("account_id")
|
||||||
) {
|
) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutateApp((draft) => {
|
App.mutate((draft) => {
|
||||||
draft.logins.wargaming = {
|
draft.logins.wargaming = {
|
||||||
expires: Number(searchParams.get('expires_at')) * 1000,
|
expires: Number(searchParams.get("expires_at")) * 1000,
|
||||||
token: searchParams.get('access_token')!,
|
token: searchParams.get("access_token")!,
|
||||||
id: Number(searchParams.get('account_id'))!,
|
id: Number(searchParams.get("account_id"))!,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -53,7 +43,7 @@ function Content({ provider }: AuthorizeProps) {
|
|||||||
throw new Error(`Unknown provider: ${provider}`);
|
throw new Error(`Unknown provider: ${provider}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location.href = searchParams.get('return') ?? '/';
|
window.location.href = searchParams.get("return") ?? "/";
|
||||||
})();
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ import {
|
|||||||
LocaleProvider,
|
LocaleProvider,
|
||||||
useLocale,
|
useLocale,
|
||||||
} from "../../../hooks/useLocale";
|
} from "../../../hooks/useLocale";
|
||||||
import { App } from "../../../stores/app";
|
|
||||||
import { CompareEphemeral } from "../../../stores/compareEphemeral";
|
import { CompareEphemeral } from "../../../stores/compareEphemeral";
|
||||||
import { TankopediaPersistent } from "../../../stores/tankopediaPersistent";
|
|
||||||
|
|
||||||
const [
|
const [
|
||||||
modelDefinitions,
|
modelDefinitions,
|
||||||
@@ -40,11 +38,7 @@ export function Page({ locale }: LocaleAcceptorProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<LocaleProvider locale={locale}>
|
<LocaleProvider locale={locale}>
|
||||||
<App.Provider>
|
<Content />
|
||||||
<TankopediaPersistent.Provider>
|
|
||||||
<Content />
|
|
||||||
</TankopediaPersistent.Provider>
|
|
||||||
</App.Provider>
|
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,36 +5,35 @@ import {
|
|||||||
ResetIcon,
|
ResetIcon,
|
||||||
TimerIcon,
|
TimerIcon,
|
||||||
WidthIcon,
|
WidthIcon,
|
||||||
} from '@radix-ui/react-icons';
|
} from "@radix-ui/react-icons";
|
||||||
import { Box, Button, Flex, Heading, ScrollArea, Text } from '@radix-ui/themes';
|
import { Box, Button, Flex, Heading, ScrollArea, Text } from "@radix-ui/themes";
|
||||||
import { useEffect, useRef, useState, type ReactNode } from 'react';
|
import { useEffect, useRef, useState, type ReactNode } from "react";
|
||||||
import { CopyButton } from '../../../../components/CopyButton';
|
import { CopyButton } from "../../../../components/CopyButton";
|
||||||
import { Boolean } from '../../../../components/Embeds/Boolean';
|
import { Boolean } from "../../../../components/Embeds/Boolean";
|
||||||
import { Color } from '../../../../components/Embeds/Color';
|
import { Color } from "../../../../components/Embeds/Color";
|
||||||
import { Enum } from '../../../../components/Embeds/Enum';
|
import { Enum } from "../../../../components/Embeds/Enum";
|
||||||
import { GenerateURL } from '../../../../components/Embeds/GenerateURL';
|
import { GenerateURL } from "../../../../components/Embeds/GenerateURL";
|
||||||
import { Import } from '../../../../components/Embeds/Import';
|
import { Import } from "../../../../components/Embeds/Import";
|
||||||
import { PreviewWrapper } from '../../../../components/Embeds/PreviewWrapper';
|
import { PreviewWrapper } from "../../../../components/Embeds/PreviewWrapper";
|
||||||
import { Radius } from '../../../../components/Embeds/Radius';
|
import { Radius } from "../../../../components/Embeds/Radius";
|
||||||
import { RichText } from '../../../../components/Embeds/RichText';
|
import { RichText } from "../../../../components/Embeds/RichText";
|
||||||
import { Size } from '../../../../components/Embeds/Size';
|
import { Size } from "../../../../components/Embeds/Size";
|
||||||
import { SizeWithout0 } from '../../../../components/Embeds/SizeWithout0';
|
import { SizeWithout0 } from "../../../../components/Embeds/SizeWithout0";
|
||||||
import { Slider } from '../../../../components/Embeds/Slider';
|
import { Slider } from "../../../../components/Embeds/Slider";
|
||||||
import { TextController } from '../../../../components/Embeds/TextController';
|
import { TextController } from "../../../../components/Embeds/TextController";
|
||||||
import { PageWrapper } from '../../../../components/PageWrapper';
|
import { PageWrapper } from "../../../../components/PageWrapper";
|
||||||
import {
|
import {
|
||||||
embedConfigurations,
|
embedConfigurations,
|
||||||
extractEmbedConfigDefaults,
|
extractEmbedConfigDefaults,
|
||||||
} from '../../../../constants/embeds';
|
} from "../../../../constants/embeds";
|
||||||
import { NAVBAR_HEIGHT } from '../../../../constants/navbar';
|
import { NAVBAR_HEIGHT } from "../../../../constants/navbar";
|
||||||
import {
|
import {
|
||||||
LocaleProvider,
|
LocaleProvider,
|
||||||
useLocale,
|
useLocale,
|
||||||
type LocaleAcceptorProps,
|
type LocaleAcceptorProps,
|
||||||
} from '../../../../hooks/useLocale';
|
} from "../../../../hooks/useLocale";
|
||||||
import { App } from '../../../../stores/app';
|
import { EmbedState, type EmbedConfig } from "../../../../stores/embedState";
|
||||||
import { EmbedState, type EmbedConfig } from '../../../../stores/embedState';
|
import { EmbedItemType } from "../../../../stores/embedState/constants";
|
||||||
import { EmbedItemType } from '../../../../stores/embedState/constants';
|
|
||||||
|
|
||||||
export interface EmbedPreviewControllerProps {
|
export interface EmbedPreviewControllerProps {
|
||||||
configKey: string;
|
configKey: string;
|
||||||
@@ -47,31 +46,28 @@ interface PageProps {
|
|||||||
export function Page({ embed, locale }: PageProps & LocaleAcceptorProps) {
|
export function Page({ embed, locale }: PageProps & LocaleAcceptorProps) {
|
||||||
const config = embedConfigurations[embed];
|
const config = embedConfigurations[embed];
|
||||||
|
|
||||||
|
EmbedState.useInitialization(extractEmbedConfigDefaults(config));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LocaleProvider locale={locale}>
|
<LocaleProvider locale={locale}>
|
||||||
<App.Provider>
|
<Content embed={embed} />
|
||||||
<EmbedState.Provider data={extractEmbedConfigDefaults(config)}>
|
|
||||||
<Content embed={embed} />
|
|
||||||
</EmbedState.Provider>
|
|
||||||
</App.Provider>
|
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Content({ embed }: PageProps) {
|
function Content({ embed }: PageProps) {
|
||||||
const embedStateStore = EmbedState.useStore();
|
|
||||||
const config = embedConfigurations[embed] as EmbedConfig;
|
const config = embedConfigurations[embed] as EmbedConfig;
|
||||||
const [backgroundImage, setBackgroundImage] = useState(
|
const [backgroundImage, setBackgroundImage] = useState(
|
||||||
'/assets/images/backgrounds/embed-default.webp',
|
"/assets/images/backgrounds/embed-default.webp"
|
||||||
);
|
);
|
||||||
const fileInput = useRef<HTMLInputElement>();
|
const fileInput = useRef<HTMLInputElement>();
|
||||||
const { strings } = useLocale();
|
const { strings } = useLocale();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fileInput.current = document.createElement('input');
|
fileInput.current = document.createElement("input");
|
||||||
fileInput.current.type = 'file';
|
fileInput.current.type = "file";
|
||||||
fileInput.current.style.display = 'none';
|
fileInput.current.style.display = "none";
|
||||||
fileInput.current.accept = 'image/*';
|
fileInput.current.accept = "image/*";
|
||||||
fileInput.current.onchange = (event) => {
|
fileInput.current.onchange = (event) => {
|
||||||
const file = (event.target as HTMLInputElement).files?.[0];
|
const file = (event.target as HTMLInputElement).files?.[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
@@ -84,7 +80,7 @@ function Content({ embed }: PageProps) {
|
|||||||
<Flex>
|
<Flex>
|
||||||
<ScrollArea
|
<ScrollArea
|
||||||
scrollbars="vertical"
|
scrollbars="vertical"
|
||||||
style={{ height: '100%', maxWidth: 320 }}
|
style={{ height: "100%", maxWidth: 320 }}
|
||||||
>
|
>
|
||||||
<Flex direction="column" gap="2" p="4">
|
<Flex direction="column" gap="2" p="4">
|
||||||
<Import />
|
<Import />
|
||||||
@@ -102,7 +98,7 @@ function Content({ embed }: PageProps) {
|
|||||||
<CopyButton
|
<CopyButton
|
||||||
variant="outline"
|
variant="outline"
|
||||||
copy={() =>
|
copy={() =>
|
||||||
'body { margin: 0; background-color: transparent; overflow: hidden; }'
|
"body { margin: 0; background-color: transparent; overflow: hidden; }"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<CodeIcon />
|
<CodeIcon />
|
||||||
@@ -110,19 +106,19 @@ function Content({ embed }: PageProps) {
|
|||||||
</CopyButton>
|
</CopyButton>
|
||||||
<CopyButton
|
<CopyButton
|
||||||
variant="outline"
|
variant="outline"
|
||||||
copy={() => `${embedStateStore.getState().width}`}
|
copy={() => `${EmbedState.state.width}`}
|
||||||
>
|
>
|
||||||
<WidthIcon />
|
<WidthIcon />
|
||||||
{strings.website.tools.embed.configuration.export.width}
|
{strings.website.tools.embed.configuration.export.width}
|
||||||
</CopyButton>
|
</CopyButton>
|
||||||
<CopyButton
|
<CopyButton
|
||||||
variant="outline"
|
variant="outline"
|
||||||
copy={() => `${embedStateStore.getState().height}`}
|
copy={() => `${EmbedState.state.height}`}
|
||||||
>
|
>
|
||||||
<HeightIcon />
|
<HeightIcon />
|
||||||
{strings.website.tools.embed.configuration.export.height}
|
{strings.website.tools.embed.configuration.export.height}
|
||||||
</CopyButton>
|
</CopyButton>
|
||||||
<CopyButton variant="outline" copy={() => '1'}>
|
<CopyButton variant="outline" copy={() => "1"}>
|
||||||
<TimerIcon />
|
<TimerIcon />
|
||||||
{strings.website.tools.embed.configuration.export.framerate}
|
{strings.website.tools.embed.configuration.export.framerate}
|
||||||
</CopyButton>
|
</CopyButton>
|
||||||
@@ -136,13 +132,10 @@ function Content({ embed }: PageProps) {
|
|||||||
color="red"
|
color="red"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
embedStateStore.setState(
|
EmbedState.set(EmbedState.initial);
|
||||||
embedStateStore.getInitialState(),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ResetIcon />{' '}
|
<ResetIcon />{" "}
|
||||||
{strings.website.tools.embed.configuration.customize.reset}
|
{strings.website.tools.embed.configuration.customize.reset}
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -211,11 +204,11 @@ function Content({ embed }: PageProps) {
|
|||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
key={configKey}
|
key={configKey}
|
||||||
direction={oneLiner ? undefined : 'column'}
|
direction={oneLiner ? undefined : "column"}
|
||||||
gap={oneLiner ? '2' : '1'}
|
gap={oneLiner ? "2" : "1"}
|
||||||
justify={oneLiner ? 'between' : undefined}
|
justify={oneLiner ? "between" : undefined}
|
||||||
mb={oneLiner ? '2' : '4'}
|
mb={oneLiner ? "2" : "4"}
|
||||||
pb={item.pad ? '6' : undefined}
|
pb={item.pad ? "6" : undefined}
|
||||||
>
|
>
|
||||||
<Text>
|
<Text>
|
||||||
{
|
{
|
||||||
@@ -229,7 +222,7 @@ function Content({ embed }: PageProps) {
|
|||||||
gap="2"
|
gap="2"
|
||||||
align="center"
|
align="center"
|
||||||
flexGrow="1"
|
flexGrow="1"
|
||||||
justify={oneLiner ? 'end' : undefined}
|
justify={oneLiner ? "end" : undefined}
|
||||||
>
|
>
|
||||||
{control}
|
{control}
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -250,9 +243,9 @@ function Content({ embed }: PageProps) {
|
|||||||
direction="column"
|
direction="column"
|
||||||
height="100%"
|
height="100%"
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 'var(--radius-3)',
|
borderRadius: "var(--radius-3)",
|
||||||
overflow: 'hidden',
|
overflow: "hidden",
|
||||||
boxShadow: 'var(--shadow-3)',
|
boxShadow: "var(--shadow-3)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
@@ -261,7 +254,7 @@ function Content({ embed }: PageProps) {
|
|||||||
p="2"
|
p="2"
|
||||||
pl="3"
|
pl="3"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'var(--color-panel-solid)',
|
backgroundColor: "var(--color-panel-solid)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text color="gray">
|
<Text color="gray">
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import { Box } from '@radix-ui/themes';
|
import { Box } from "@radix-ui/themes";
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from "react";
|
||||||
import { parse } from 'urlon';
|
import { parse } from "urlon";
|
||||||
import { BlitzKitTheme } from '../../../../../components/BlitzKitTheme';
|
import { BlitzKitTheme } from "../../../../../components/BlitzKitTheme";
|
||||||
import {
|
import {
|
||||||
embedConfigurations,
|
embedConfigurations,
|
||||||
embedRenderers,
|
embedRenderers,
|
||||||
extractEmbedConfigDefaults,
|
extractEmbedConfigDefaults,
|
||||||
} from '../../../../../constants/embeds';
|
} from "../../../../../constants/embeds";
|
||||||
import {
|
import {
|
||||||
LocaleProvider,
|
LocaleProvider,
|
||||||
type LocaleAcceptorProps,
|
type LocaleAcceptorProps,
|
||||||
} from '../../../../../hooks/useLocale';
|
} from "../../../../../hooks/useLocale";
|
||||||
import {
|
import {
|
||||||
EmbedState,
|
EmbedState,
|
||||||
type EmbedConfig,
|
type EmbedConfig,
|
||||||
type ExtractEmbedConfigTypes,
|
type ExtractEmbedConfigTypes,
|
||||||
} from '../../../../../stores/embedState';
|
} from "../../../../../stores/embedState";
|
||||||
import { useEmbedStateCurry } from '../../../../../stores/embedState/utilities';
|
import { useEmbedStateCurry } from "../../../../../stores/embedState/utilities";
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
embed: keyof typeof embedConfigurations;
|
embed: keyof typeof embedConfigurations;
|
||||||
@@ -25,7 +25,7 @@ interface PageProps {
|
|||||||
export function Page({ embed, locale }: PageProps & LocaleAcceptorProps) {
|
export function Page({ embed, locale }: PageProps & LocaleAcceptorProps) {
|
||||||
const state = useMemo(() => {
|
const state = useMemo(() => {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const stateRaw = params.get('state');
|
const stateRaw = params.get("state");
|
||||||
const partialState = stateRaw === null ? {} : parse(stateRaw);
|
const partialState = stateRaw === null ? {} : parse(stateRaw);
|
||||||
const state = {
|
const state = {
|
||||||
...extractEmbedConfigDefaults(embedConfigurations[embed]),
|
...extractEmbedConfigDefaults(embedConfigurations[embed]),
|
||||||
@@ -35,11 +35,11 @@ export function Page({ embed, locale }: PageProps & LocaleAcceptorProps) {
|
|||||||
return state;
|
return state;
|
||||||
}, [embed]);
|
}, [embed]);
|
||||||
|
|
||||||
|
EmbedState.useInitialization(state);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LocaleProvider locale={locale}>
|
<LocaleProvider locale={locale}>
|
||||||
<EmbedState.Provider data={state}>
|
<Content embed={embed} />
|
||||||
<Content embed={embed} />
|
|
||||||
</EmbedState.Provider>
|
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -47,11 +47,11 @@ export function Page({ embed, locale }: PageProps & LocaleAcceptorProps) {
|
|||||||
function Content({ embed }: PageProps) {
|
function Content({ embed }: PageProps) {
|
||||||
const Renderer = embedRenderers[embed];
|
const Renderer = embedRenderers[embed];
|
||||||
const { useEmbedState } = useEmbedStateCurry();
|
const { useEmbedState } = useEmbedStateCurry();
|
||||||
const width = useEmbedState('width');
|
const width = useEmbedState("width");
|
||||||
const height = useEmbedState('height');
|
const height = useEmbedState("height");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BlitzKitTheme style={{ background: 'transparent' }}>
|
<BlitzKitTheme style={{ background: "transparent" }}>
|
||||||
<Box width={`${width}px`} height={`${height}px`} overflow="hidden">
|
<Box width={`${width}px`} height={`${height}px`} overflow="hidden">
|
||||||
<Renderer />
|
<Renderer />
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,26 +1,19 @@
|
|||||||
import { Box, Flex, Link, Text } from '@radix-ui/themes';
|
import { Box, Flex, Link, Text } from "@radix-ui/themes";
|
||||||
import { PreviewWrapper } from '../../../components/Embeds/PreviewWrapper';
|
import { PreviewWrapper } from "../../../components/Embeds/PreviewWrapper";
|
||||||
import { LinkI18n } from '../../../components/LinkI18n';
|
import { LinkI18n } from "../../../components/LinkI18n";
|
||||||
import { PageWrapper } from '../../../components/PageWrapper';
|
import { PageWrapper } from "../../../components/PageWrapper";
|
||||||
import {
|
import { embedConfigurations } from "../../../constants/embeds";
|
||||||
embedConfigurations,
|
import { Var } from "../../../core/radix/var";
|
||||||
extractEmbedConfigDefaults,
|
|
||||||
} from '../../../constants/embeds';
|
|
||||||
import { Var } from '../../../core/radix/var';
|
|
||||||
import {
|
import {
|
||||||
type LocaleAcceptorProps,
|
type LocaleAcceptorProps,
|
||||||
LocaleProvider,
|
LocaleProvider,
|
||||||
useLocale,
|
useLocale,
|
||||||
} from '../../../hooks/useLocale';
|
} from "../../../hooks/useLocale";
|
||||||
import { App } from '../../../stores/app';
|
|
||||||
import { EmbedState } from '../../../stores/embedState';
|
|
||||||
|
|
||||||
export function Page({ locale }: LocaleAcceptorProps) {
|
export function Page({ locale }: LocaleAcceptorProps) {
|
||||||
return (
|
return (
|
||||||
<LocaleProvider locale={locale}>
|
<LocaleProvider locale={locale}>
|
||||||
<App.Provider>
|
<Content />
|
||||||
<Content />
|
|
||||||
</App.Provider>
|
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -31,7 +24,7 @@ function Content() {
|
|||||||
return (
|
return (
|
||||||
<PageWrapper color="red">
|
<PageWrapper color="red">
|
||||||
<Text align="center" mt="3">
|
<Text align="center" mt="3">
|
||||||
{strings.website.tools.embed.coming_soon}{' '}
|
{strings.website.tools.embed.coming_soon}{" "}
|
||||||
<Link
|
<Link
|
||||||
underline="always"
|
underline="always"
|
||||||
href="https://discord.gg/nDt7AjGJQH"
|
href="https://discord.gg/nDt7AjGJQH"
|
||||||
@@ -42,61 +35,56 @@ function Content() {
|
|||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Flex justify="center">
|
<Flex justify="center">
|
||||||
{Object.entries(embedConfigurations).map(([embed, config]) => (
|
{Object.keys(embedConfigurations).map((embed) => (
|
||||||
<EmbedState.Provider
|
<LinkI18n
|
||||||
key={embed}
|
locale={locale}
|
||||||
data={extractEmbedConfigDefaults(config)}
|
href={`/embed/${embed}`}
|
||||||
|
color="gray"
|
||||||
|
highContrast
|
||||||
|
style={{
|
||||||
|
width: "fit-content",
|
||||||
|
height: "fit-content",
|
||||||
|
}}
|
||||||
|
underline="hover"
|
||||||
>
|
>
|
||||||
<LinkI18n
|
<Flex
|
||||||
locale={locale}
|
direction="column"
|
||||||
href={`/embed/${embed}`}
|
overflow="hidden"
|
||||||
color="gray"
|
|
||||||
highContrast
|
|
||||||
style={{
|
style={{
|
||||||
width: 'fit-content',
|
borderRadius: Var("radius-4"),
|
||||||
height: 'fit-content',
|
backgroundColor: Var("color-panel-solid"),
|
||||||
|
boxShadow: Var("shadow-3"),
|
||||||
}}
|
}}
|
||||||
underline="hover"
|
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex justify="center" p="2">
|
||||||
direction="column"
|
<Text>
|
||||||
|
{
|
||||||
|
strings.website.tools.embed.types[
|
||||||
|
embed as keyof typeof strings.website.tools.embed.types
|
||||||
|
].name
|
||||||
|
}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
width="20rem"
|
||||||
|
height="20rem"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
|
position="relative"
|
||||||
style={{
|
style={{
|
||||||
borderRadius: Var('radius-4'),
|
backgroundImage:
|
||||||
backgroundColor: Var('color-panel-solid'),
|
"url(/assets/images/backgrounds/embed-default.webp)",
|
||||||
boxShadow: Var('shadow-3'),
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex justify="center" p="2">
|
<PreviewWrapper
|
||||||
<Text>
|
naked
|
||||||
{
|
name={embed as keyof typeof embedConfigurations}
|
||||||
strings.website.tools.embed.types[
|
/>
|
||||||
embed as keyof typeof strings.website.tools.embed.types
|
</Box>
|
||||||
].name
|
</Flex>
|
||||||
}
|
</LinkI18n>
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
width="20rem"
|
|
||||||
height="20rem"
|
|
||||||
overflow="hidden"
|
|
||||||
position="relative"
|
|
||||||
style={{
|
|
||||||
backgroundImage:
|
|
||||||
'url(/assets/images/backgrounds/embed-default.webp)',
|
|
||||||
backgroundSize: 'cover',
|
|
||||||
backgroundPosition: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PreviewWrapper
|
|
||||||
naked
|
|
||||||
name={embed as keyof typeof embedConfigurations}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
</LinkI18n>
|
|
||||||
</EmbedState.Provider>
|
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { Guesser } from "../../../components/Guesser";
|
|||||||
import { GuessRenderer } from "../../../components/GuessRenderer";
|
import { GuessRenderer } from "../../../components/GuessRenderer";
|
||||||
import { GuessRendererLoader } from "../../../components/GuessRendererLoader";
|
import { GuessRendererLoader } from "../../../components/GuessRendererLoader";
|
||||||
import { awaitableModelDefinitions } from "../../../core/awaitables/modelDefinitions";
|
import { awaitableModelDefinitions } from "../../../core/awaitables/modelDefinitions";
|
||||||
import { awaitableProvisionDefinitions } from "../../../core/awaitables/provisionDefinitions";
|
|
||||||
import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions";
|
import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions";
|
||||||
import {
|
import {
|
||||||
type LocaleAcceptorProps,
|
type LocaleAcceptorProps,
|
||||||
@@ -16,12 +15,10 @@ import { Duel } from "../../../stores/duel";
|
|||||||
import { Guess, GuessState } from "../../../stores/guess";
|
import { Guess, GuessState } from "../../../stores/guess";
|
||||||
import { Tankopedia } from "../../../stores/tankopedia";
|
import { Tankopedia } from "../../../stores/tankopedia";
|
||||||
|
|
||||||
const [tankDefinitions, modelDefinitions, provisionDefinitions] =
|
const [tankDefinitions, modelDefinitions] = await Promise.all([
|
||||||
await Promise.all([
|
awaitableTankDefinitions,
|
||||||
awaitableTankDefinitions,
|
awaitableModelDefinitions,
|
||||||
awaitableModelDefinitions,
|
]);
|
||||||
awaitableProvisionDefinitions,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ids = Object.keys(tankDefinitions.tanks);
|
const ids = Object.keys(tankDefinitions.tanks);
|
||||||
|
|
||||||
@@ -43,12 +40,9 @@ function Container() {
|
|||||||
const model = modelDefinitions.models[tank.id];
|
const model = modelDefinitions.models[tank.id];
|
||||||
|
|
||||||
Tankopedia.useInitialization(model);
|
Tankopedia.useInitialization(model);
|
||||||
|
Duel.useInitialization({ tank, model });
|
||||||
|
|
||||||
return (
|
return <Content />;
|
||||||
<Duel.Provider data={{ tank, model, provisionDefinitions }}>
|
|
||||||
<Content />
|
|
||||||
</Duel.Provider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Content() {
|
function Content() {
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
import { PageWrapper } from '../../../components/PageWrapper';
|
import { PageWrapper } from "../../../components/PageWrapper";
|
||||||
import { PerformanceInfo } from '../../../components/Performance/PerformanceInfo';
|
import { PerformanceInfo } from "../../../components/Performance/PerformanceInfo";
|
||||||
import { PlayerCountControl } from '../../../components/Performance/PlayerCountControl';
|
import { PlayerCountControl } from "../../../components/Performance/PlayerCountControl";
|
||||||
import { TankTable } from '../../../components/Performance/Table';
|
import { TankTable } from "../../../components/Performance/Table";
|
||||||
import { FilterControl } from '../../../components/TankSearch/components/FilterControl';
|
import { FilterControl } from "../../../components/TankSearch/components/FilterControl";
|
||||||
import {
|
import {
|
||||||
LocaleProvider,
|
LocaleProvider,
|
||||||
type LocaleAcceptorProps,
|
type LocaleAcceptorProps,
|
||||||
} from '../../../hooks/useLocale';
|
} from "../../../hooks/useLocale";
|
||||||
import { TankPerformanceSort } from '../../../stores/tankPerformanceSort';
|
import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps";
|
||||||
import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps';
|
|
||||||
|
|
||||||
type PageProps = MaybeSkeletonComponentProps & LocaleAcceptorProps;
|
type PageProps = MaybeSkeletonComponentProps & LocaleAcceptorProps;
|
||||||
|
|
||||||
export function Page({ skeleton, locale }: PageProps) {
|
export function Page({ skeleton, locale }: PageProps) {
|
||||||
return (
|
return (
|
||||||
<LocaleProvider locale={locale}>
|
<LocaleProvider locale={locale}>
|
||||||
<TankPerformanceSort.Provider>
|
<Content skeleton={skeleton} />
|
||||||
<Content skeleton={skeleton} />
|
|
||||||
</TankPerformanceSort.Provider>
|
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,24 @@
|
|||||||
import { DEFAULT_LOCALE } from '@blitzkit/i18n';
|
import { DEFAULT_LOCALE } from "@blitzkit/i18n";
|
||||||
import { Flex, Switch, Text } from '@radix-ui/themes';
|
import { Flex, Switch, Text } from "@radix-ui/themes";
|
||||||
import { LocaleSwitcher } from '../../../components/LocaleSwitcher';
|
import { LocaleSwitcher } from "../../../components/LocaleSwitcher";
|
||||||
import { PageWrapper } from '../../../components/PageWrapper';
|
import { PageWrapper } from "../../../components/PageWrapper";
|
||||||
import {
|
import {
|
||||||
LocaleProvider,
|
LocaleProvider,
|
||||||
useLocale,
|
useLocale,
|
||||||
type LocaleAcceptorProps,
|
type LocaleAcceptorProps,
|
||||||
} from '../../../hooks/useLocale';
|
} from "../../../hooks/useLocale";
|
||||||
import { App } from '../../../stores/app';
|
import { App } from "../../../stores/app";
|
||||||
|
|
||||||
export function Page({ locale = DEFAULT_LOCALE }: LocaleAcceptorProps) {
|
export function Page({ locale = DEFAULT_LOCALE }: LocaleAcceptorProps) {
|
||||||
return (
|
return (
|
||||||
<LocaleProvider locale={locale}>
|
<LocaleProvider locale={locale}>
|
||||||
<App.Provider>
|
<Content locale={locale} />
|
||||||
<Content locale={locale} />
|
|
||||||
</App.Provider>
|
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 appStore = App.useStore();
|
|
||||||
const { strings } = useLocale();
|
const { strings } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -33,7 +30,9 @@ function Content({ locale }: LocaleAcceptorProps) {
|
|||||||
variant="classic"
|
variant="classic"
|
||||||
checked={developerMode}
|
checked={developerMode}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) =>
|
||||||
appStore.setState({ developerMode: checked })
|
App.mutate((draft) => {
|
||||||
|
draft.developerMode = checked;
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -9,16 +9,13 @@ import { MetaSection } from "../../../../components/Tankopedia/MetaSection";
|
|||||||
import { TechTreeSection } from "../../../../components/Tankopedia/TechTreeSection";
|
import { TechTreeSection } from "../../../../components/Tankopedia/TechTreeSection";
|
||||||
import { VideoSection } from "../../../../components/Tankopedia/VideoSection";
|
import { VideoSection } from "../../../../components/Tankopedia/VideoSection";
|
||||||
import { awaitableModelDefinitions } from "../../../../core/awaitables/modelDefinitions";
|
import { awaitableModelDefinitions } from "../../../../core/awaitables/modelDefinitions";
|
||||||
import { awaitableProvisionDefinitions } from "../../../../core/awaitables/provisionDefinitions";
|
|
||||||
import { awaitableTankDefinitions } from "../../../../core/awaitables/tankDefinitions";
|
import { awaitableTankDefinitions } from "../../../../core/awaitables/tankDefinitions";
|
||||||
import {
|
import {
|
||||||
LocaleProvider,
|
LocaleProvider,
|
||||||
type LocaleAcceptorProps,
|
type LocaleAcceptorProps,
|
||||||
} from "../../../../hooks/useLocale";
|
} from "../../../../hooks/useLocale";
|
||||||
import { App } from "../../../../stores/app";
|
|
||||||
import { Duel } from "../../../../stores/duel";
|
import { Duel } from "../../../../stores/duel";
|
||||||
import { Tankopedia } from "../../../../stores/tankopedia";
|
import { Tankopedia } from "../../../../stores/tankopedia";
|
||||||
import { TankopediaPersistent } from "../../../../stores/tankopediaPersistent";
|
|
||||||
import type { MaybeSkeletonComponentProps } from "../../../../types/maybeSkeletonComponentProps";
|
import type { MaybeSkeletonComponentProps } from "../../../../types/maybeSkeletonComponentProps";
|
||||||
import type { TankGuide } from "./index.astro";
|
import type { TankGuide } from "./index.astro";
|
||||||
|
|
||||||
@@ -28,41 +25,32 @@ type PageProps = MaybeSkeletonComponentProps &
|
|||||||
guide?: TankGuide;
|
guide?: TankGuide;
|
||||||
};
|
};
|
||||||
|
|
||||||
const [tankDefinitions, provisionDefinitions, modelDefinitions] =
|
const [tankDefinitions, modelDefinitions] = await Promise.all([
|
||||||
await Promise.all([
|
awaitableTankDefinitions,
|
||||||
awaitableTankDefinitions,
|
awaitableModelDefinitions,
|
||||||
awaitableProvisionDefinitions,
|
]);
|
||||||
awaitableModelDefinitions,
|
|
||||||
]);
|
|
||||||
|
|
||||||
export function Page({ id, skeleton, locale, guide }: PageProps) {
|
export function Page({ id, skeleton, locale, guide }: PageProps) {
|
||||||
const tank = tankDefinitions.tanks[id];
|
const tank = tankDefinitions.tanks[id];
|
||||||
const model = modelDefinitions.models[id];
|
const model = modelDefinitions.models[id];
|
||||||
|
|
||||||
Tankopedia.useInitialization(model);
|
Tankopedia.useInitialization(model);
|
||||||
|
Duel.useInitialization({ tank, model });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LocaleProvider locale={locale}>
|
<LocaleProvider locale={locale}>
|
||||||
<App.Provider>
|
<PageWrapper p="0" maxWidth="unset" color="purple" gap="9" pb="9">
|
||||||
<TankopediaPersistent.Provider>
|
<HeroSection skeleton={skeleton} />
|
||||||
<Duel.Provider
|
<MetaSection />
|
||||||
data={{ tank, model, provisionDefinitions: provisionDefinitions }}
|
<CalloutsSection />
|
||||||
>
|
{tank.type === TankType.RESEARCHABLE && !tank.deprecated && (
|
||||||
<PageWrapper p="0" maxWidth="unset" color="purple" gap="9" pb="9">
|
<TechTreeSection skeleton={skeleton} />
|
||||||
<HeroSection skeleton={skeleton} />
|
)}
|
||||||
<MetaSection />
|
<CharacteristicsSection skeleton={skeleton} />
|
||||||
<CalloutsSection />
|
<GameModeSection />
|
||||||
{tank.type === TankType.RESEARCHABLE && !tank.deprecated && (
|
{guide && <GuideSection guide={guide} />}
|
||||||
<TechTreeSection skeleton={skeleton} />
|
<VideoSection skeleton={skeleton} />
|
||||||
)}
|
</PageWrapper>
|
||||||
<CharacteristicsSection skeleton={skeleton} />
|
|
||||||
<GameModeSection />
|
|
||||||
{guide && <GuideSection guide={guide} />}
|
|
||||||
<VideoSection skeleton={skeleton} />
|
|
||||||
</PageWrapper>
|
|
||||||
</Duel.Provider>
|
|
||||||
</TankopediaPersistent.Provider>
|
|
||||||
</App.Provider>
|
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
import { PageWrapper } from '../../../components/PageWrapper';
|
import { PageWrapper } from "../../../components/PageWrapper";
|
||||||
import { TankSearch } from '../../../components/TankSearch';
|
import { TankSearch } from "../../../components/TankSearch";
|
||||||
import {
|
import {
|
||||||
LocaleProvider,
|
LocaleProvider,
|
||||||
type LocaleAcceptorProps,
|
type LocaleAcceptorProps,
|
||||||
} from '../../../hooks/useLocale';
|
} from "../../../hooks/useLocale";
|
||||||
import { App } from '../../../stores/app';
|
import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps";
|
||||||
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
|
|
||||||
import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps';
|
|
||||||
|
|
||||||
export function Page({
|
export function Page({
|
||||||
locale,
|
locale,
|
||||||
@@ -15,11 +13,7 @@ export function Page({
|
|||||||
return (
|
return (
|
||||||
<LocaleProvider locale={locale}>
|
<LocaleProvider locale={locale}>
|
||||||
<PageWrapper color="purple" maxWidth="80rem">
|
<PageWrapper color="purple" maxWidth="80rem">
|
||||||
<TankopediaPersistent.Provider>
|
<TankSearch skeleton={skeleton} />
|
||||||
<App.Provider>
|
|
||||||
<TankSearch skeleton={skeleton} />
|
|
||||||
</App.Provider>
|
|
||||||
</TankopediaPersistent.Provider>
|
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
import { PageWrapper } from '../../../components/PageWrapper';
|
import { PageWrapper } from "../../../components/PageWrapper";
|
||||||
import { FilterControl } from '../../../components/TankSearch/components/FilterControl';
|
import { FilterControl } from "../../../components/TankSearch/components/FilterControl";
|
||||||
import { TierListControls } from '../../../components/TierList/Controls';
|
import { TierListControls } from "../../../components/TierList/Controls";
|
||||||
import { TierListTable } from '../../../components/TierList/Table';
|
import { TierListTable } from "../../../components/TierList/Table";
|
||||||
import { TierListTiles } from '../../../components/TierList/Tiles';
|
import { TierListTiles } from "../../../components/TierList/Tiles";
|
||||||
import { URLManager } from '../../../components/TierList/URLManager';
|
import { URLManager } from "../../../components/TierList/URLManager";
|
||||||
import {
|
import {
|
||||||
LocaleProvider,
|
LocaleProvider,
|
||||||
type LocaleAcceptorProps,
|
type LocaleAcceptorProps,
|
||||||
} from '../../../hooks/useLocale';
|
} from "../../../hooks/useLocale";
|
||||||
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
|
|
||||||
|
|
||||||
export function Page({ locale }: LocaleAcceptorProps) {
|
export function Page({ locale }: LocaleAcceptorProps) {
|
||||||
return (
|
return (
|
||||||
<LocaleProvider locale={locale}>
|
<LocaleProvider locale={locale}>
|
||||||
<TankopediaPersistent.Provider>
|
<Content />
|
||||||
<Content />
|
|
||||||
</TankopediaPersistent.Provider>
|
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,14 @@
|
|||||||
import { TankSandbox } from "../../../../../components/Tankopedia/HeroSection/components/TankSandbox";
|
import { TankSandbox } from "../../../../../components/Tankopedia/HeroSection/components/TankSandbox";
|
||||||
import { awaitableModelDefinitions } from "../../../../../core/awaitables/modelDefinitions";
|
import { awaitableModelDefinitions } from "../../../../../core/awaitables/modelDefinitions";
|
||||||
import { awaitableProvisionDefinitions } from "../../../../../core/awaitables/provisionDefinitions";
|
|
||||||
import { awaitableTankDefinitions } from "../../../../../core/awaitables/tankDefinitions";
|
import { awaitableTankDefinitions } from "../../../../../core/awaitables/tankDefinitions";
|
||||||
import { LocaleProvider } from "../../../../../hooks/useLocale";
|
import { LocaleProvider } from "../../../../../hooks/useLocale";
|
||||||
import { App } from "../../../../../stores/app";
|
|
||||||
import { Duel } from "../../../../../stores/duel";
|
import { Duel } from "../../../../../stores/duel";
|
||||||
import { Tankopedia } from "../../../../../stores/tankopedia";
|
import { Tankopedia } from "../../../../../stores/tankopedia";
|
||||||
import { TankopediaPersistent } from "../../../../../stores/tankopediaPersistent";
|
|
||||||
|
|
||||||
const [provisionDefinitions, tankDefinitions, modelDefinitions] =
|
const [tankDefinitions, modelDefinitions] = await Promise.all([
|
||||||
await Promise.all([
|
awaitableTankDefinitions,
|
||||||
awaitableProvisionDefinitions,
|
awaitableModelDefinitions,
|
||||||
awaitableTankDefinitions,
|
]);
|
||||||
awaitableModelDefinitions,
|
|
||||||
]);
|
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -24,16 +19,11 @@ export function Page({ id }: PageProps) {
|
|||||||
const model = modelDefinitions.models[id];
|
const model = modelDefinitions.models[id];
|
||||||
|
|
||||||
Tankopedia.useInitialization(model);
|
Tankopedia.useInitialization(model);
|
||||||
|
Duel.useInitialization({ tank, model });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LocaleProvider locale="en">
|
<LocaleProvider locale="en">
|
||||||
<App.Provider>
|
<Content />
|
||||||
<TankopediaPersistent.Provider>
|
|
||||||
<Duel.Provider data={{ provisionDefinitions, model, tank }}>
|
|
||||||
<Content />
|
|
||||||
</Duel.Provider>
|
|
||||||
</TankopediaPersistent.Provider>
|
|
||||||
</App.Provider>
|
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import { merge } from 'lodash-es';
|
import { Varuna } from "varuna";
|
||||||
import { create } from 'zustand';
|
|
||||||
import { persist, subscribeWithSelector } from 'zustand/middleware';
|
|
||||||
import { createContextualStore } from '../../core/zustand/createContextualStore';
|
|
||||||
|
|
||||||
export interface WargamingLogin {
|
export interface WargamingLogin {
|
||||||
token: string;
|
token: string;
|
||||||
@@ -9,7 +6,7 @@ export interface WargamingLogin {
|
|||||||
expires: number;
|
expires: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AppStore {
|
interface App {
|
||||||
developerMode: boolean;
|
developerMode: boolean;
|
||||||
policiesAgreementIndex: number;
|
policiesAgreementIndex: number;
|
||||||
logins: {
|
logins: {
|
||||||
@@ -17,15 +14,11 @@ interface AppStore {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const App = createContextualStore(() =>
|
export const App = new Varuna<App>(
|
||||||
create<AppStore>()(
|
{
|
||||||
persist(
|
developerMode: false,
|
||||||
subscribeWithSelector<AppStore>(() => ({
|
policiesAgreementIndex: -1,
|
||||||
developerMode: false,
|
logins: {},
|
||||||
policiesAgreementIndex: -1,
|
},
|
||||||
logins: {},
|
"app-2"
|
||||||
})),
|
|
||||||
{ name: 'app', merge: (a, b) => merge(b, a) },
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import type { Samples } from '@blitzkit/core';
|
|
||||||
import { create } from 'zustand';
|
|
||||||
import { createContextualStore } from '../core/zustand/createContextualStore';
|
|
||||||
|
|
||||||
export interface Charts {
|
|
||||||
period: ChartsPeriod;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ChartsPeriod = keyof Samples;
|
|
||||||
|
|
||||||
export const Charts = createContextualStore(() =>
|
|
||||||
create<Charts>()(() => ({
|
|
||||||
period: 'd_30',
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
@@ -2,16 +2,14 @@ import {
|
|||||||
ModelDefinition,
|
ModelDefinition,
|
||||||
type EngineDefinition,
|
type EngineDefinition,
|
||||||
type GunDefinition,
|
type GunDefinition,
|
||||||
type ProvisionDefinitions,
|
|
||||||
type ShellDefinition,
|
type ShellDefinition,
|
||||||
type TankDefinition,
|
type TankDefinition,
|
||||||
type TrackDefinition,
|
type TrackDefinition,
|
||||||
type TurretDefinition,
|
type TurretDefinition,
|
||||||
} from '@blitzkit/core';
|
} from "@blitzkit/core";
|
||||||
import { create } from 'zustand';
|
import { Varuna } from "varuna";
|
||||||
import { subscribeWithSelector } from 'zustand/middleware';
|
import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions";
|
||||||
import { tankToDuelMember } from '../../core/blitzkit/tankToDuelMember';
|
import { tankToDuelMember } from "../../core/blitzkit/tankToDuelMember";
|
||||||
import { createContextualStore } from '../../core/zustand/createContextualStore';
|
|
||||||
|
|
||||||
type EquipmentMatrixItem = -1 | 0 | 1;
|
type EquipmentMatrixItem = -1 | 0 | 1;
|
||||||
type EquipmentMatrixRow = [
|
type EquipmentMatrixRow = [
|
||||||
@@ -45,24 +43,13 @@ export interface DuelStore {
|
|||||||
antagonist: DuelMember;
|
antagonist: DuelMember;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Duel = createContextualStore(
|
const provisionDefinitions = await awaitableProvisionDefinitions;
|
||||||
({
|
|
||||||
tank,
|
|
||||||
model,
|
|
||||||
provisionDefinitions,
|
|
||||||
}: {
|
|
||||||
tank: TankDefinition;
|
|
||||||
model: ModelDefinition;
|
|
||||||
provisionDefinitions: ProvisionDefinitions;
|
|
||||||
}) => {
|
|
||||||
const protagonist = tankToDuelMember(tank, provisionDefinitions);
|
|
||||||
|
|
||||||
return create<DuelStore>()(
|
export const Duel = new Varuna<
|
||||||
subscribeWithSelector<DuelStore>(() => ({
|
DuelStore,
|
||||||
protagonist,
|
{ tank: TankDefinition; model: ModelDefinition }
|
||||||
model,
|
>(({ tank, model }) => {
|
||||||
antagonist: protagonist,
|
const protagonist = tankToDuelMember(tank, provisionDefinitions);
|
||||||
})),
|
|
||||||
);
|
return { protagonist, antagonist: protagonist, model };
|
||||||
},
|
});
|
||||||
);
|
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
import * as radixColors from '@radix-ui/colors';
|
import * as radixColors from "@radix-ui/colors";
|
||||||
import { create } from 'zustand';
|
import { Varuna } from "varuna";
|
||||||
import { createContextualStore } from '../../core/zustand/createContextualStore';
|
|
||||||
import {
|
import {
|
||||||
type EmbedConfigItemType,
|
type EmbedConfigItemType,
|
||||||
EmbedItemType,
|
EmbedItemType,
|
||||||
radixGrays,
|
radixGrays,
|
||||||
radixTextWeights,
|
radixTextWeights,
|
||||||
} from './constants';
|
} from "./constants";
|
||||||
|
|
||||||
export type RadixSize = `${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`;
|
export type RadixSize = `${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`;
|
||||||
export type RadixSizeWithout0 = Exclude<RadixSize, '0'>;
|
export type RadixSizeWithout0 = Exclude<RadixSize, "0">;
|
||||||
export type RadixRadius = `${1 | 2 | 3 | 4}` | 'full';
|
export type RadixRadius = `${1 | 2 | 3 | 4}` | "full";
|
||||||
export type RadixTextWeight = (typeof radixTextWeights)[number];
|
export type RadixTextWeight = (typeof radixTextWeights)[number];
|
||||||
export type RadixColorCompound = {
|
export type RadixColorCompound = {
|
||||||
base: RadixColor;
|
base: RadixColor;
|
||||||
@@ -18,7 +17,7 @@ export type RadixColorCompound = {
|
|||||||
};
|
};
|
||||||
export type RadixTextColor =
|
export type RadixTextColor =
|
||||||
| Exclude<RadixColor, RadixColorGrays>
|
| Exclude<RadixColor, RadixColorGrays>
|
||||||
| 'gray'
|
| "gray"
|
||||||
| undefined;
|
| undefined;
|
||||||
export type RadixColorVariantRaw = `${
|
export type RadixColorVariantRaw = `${
|
||||||
| 1
|
| 1
|
||||||
@@ -33,17 +32,17 @@ export type RadixColorVariantRaw = `${
|
|||||||
| 10
|
| 10
|
||||||
| 11
|
| 11
|
||||||
| 12}`;
|
| 12}`;
|
||||||
export type RadixColorVariant = `${'' | 'a'}${RadixColorVariantRaw}`;
|
export type RadixColorVariant = `${"" | "a"}${RadixColorVariantRaw}`;
|
||||||
export type RadixColor = Exclude<
|
export type RadixColor = Exclude<
|
||||||
keyof typeof radixColors,
|
keyof typeof radixColors,
|
||||||
`${string}${'A' | 'P3' | 'Dark'}`
|
`${string}${"A" | "P3" | "Dark"}`
|
||||||
>;
|
>;
|
||||||
export type RadixColorGrays = (typeof radixGrays)[number];
|
export type RadixColorGrays = (typeof radixGrays)[number];
|
||||||
|
|
||||||
export type ExtractEmbedConfigTypes<Config extends EmbedConfig> = {
|
export type ExtractEmbedConfigTypes<Config extends EmbedConfig> = {
|
||||||
[Key in keyof Config]: ({
|
[Key in keyof Config]: ({
|
||||||
type: Config[Key]['type'];
|
type: Config[Key]["type"];
|
||||||
} & EmbedConfigItem)['default'];
|
} & EmbedConfigItem)["default"];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EmbedConfig = {
|
export type EmbedConfig = {
|
||||||
@@ -102,8 +101,8 @@ export type EmbedConfigItem = (
|
|||||||
pad?: boolean;
|
pad?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EmbedStateStore = Record<string, EmbedConfigItem['default']>;
|
export type EmbedStateStore = Record<string, EmbedConfigItem["default"]>;
|
||||||
|
|
||||||
export const EmbedState = createContextualStore((init: EmbedStateStore) =>
|
export const EmbedState = new Varuna<EmbedStateStore, EmbedStateStore>(
|
||||||
create<EmbedStateStore>()(() => init),
|
(data) => data
|
||||||
);
|
);
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import type en from '@blitzkit/i18n/strings/en.json';
|
import type en from "@blitzkit/i18n/strings/en.json";
|
||||||
import { create } from 'zustand';
|
import { Varuna } from "varuna";
|
||||||
import { createContextualStore } from '../../core/zustand/createContextualStore';
|
|
||||||
|
|
||||||
export type TankPerformanceSortType =
|
export type TankPerformanceSortType =
|
||||||
keyof typeof en.website.tools.performance.table.stats;
|
keyof typeof en.website.tools.performance.table.stats;
|
||||||
@@ -10,9 +9,7 @@ export interface TankPerformanceSort {
|
|||||||
direction: -1 | 1;
|
direction: -1 | 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TankPerformanceSort = createContextualStore(() =>
|
export const TankPerformanceSort = new Varuna<TankPerformanceSort>({
|
||||||
create<TankPerformanceSort>()(() => ({
|
type: "winrate",
|
||||||
type: 'winrate',
|
direction: -1,
|
||||||
direction: -1,
|
});
|
||||||
})),
|
|
||||||
);
|
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
import type en from '@blitzkit/i18n/strings/en.json';
|
import type en from "@blitzkit/i18n/strings/en.json";
|
||||||
import * as lodash from 'lodash-es';
|
import { Varuna } from "varuna";
|
||||||
import { create } from 'zustand';
|
|
||||||
import { persist, subscribeWithSelector } from 'zustand/middleware';
|
|
||||||
import { createContextualStore } from '../../core/zustand/createContextualStore';
|
|
||||||
|
|
||||||
export type TankopediaSortBy = keyof typeof en.website.common.tank_search.sort;
|
export type TankopediaSortBy = keyof typeof en.website.common.tank_search.sort;
|
||||||
export type TankopediaSortDirection = 'ascending' | 'descending';
|
export type TankopediaSortDirection = "ascending" | "descending";
|
||||||
export type TankopediaTestTankDisplay = 'include' | 'exclude' | 'only';
|
export type TankopediaTestTankDisplay = "include" | "exclude" | "only";
|
||||||
|
|
||||||
export interface TankopediaPersistentStore {
|
export interface TankopediaPersistent {
|
||||||
wireframe: boolean;
|
wireframe: boolean;
|
||||||
opaque: boolean;
|
opaque: boolean;
|
||||||
advancedHighlighting: boolean;
|
advancedHighlighting: boolean;
|
||||||
@@ -25,25 +22,21 @@ export interface TankopediaPersistentStore {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TankopediaPersistent = createContextualStore(() =>
|
export const TankopediaPersistent = new Varuna<TankopediaPersistent>(
|
||||||
create<TankopediaPersistentStore>()(
|
{
|
||||||
persist(
|
hideTankModelUnderArmor: false,
|
||||||
subscribeWithSelector<TankopediaPersistentStore>(() => ({
|
wireframe: false,
|
||||||
hideTankModelUnderArmor: false,
|
opaque: false,
|
||||||
wireframe: false,
|
advancedHighlighting: false,
|
||||||
opaque: false,
|
greenPenetration: false,
|
||||||
advancedHighlighting: false,
|
showSpacedArmor: true,
|
||||||
greenPenetration: false,
|
showExternalModules: true,
|
||||||
showSpacedArmor: true,
|
showPrimaryArmor: true,
|
||||||
showExternalModules: true,
|
recentlyViewed: [],
|
||||||
showPrimaryArmor: true,
|
sort: {
|
||||||
recentlyViewed: [],
|
by: "meta.none",
|
||||||
sort: {
|
direction: "ascending",
|
||||||
by: 'meta.none',
|
},
|
||||||
direction: 'ascending',
|
},
|
||||||
},
|
"tankopedia-2"
|
||||||
})),
|
|
||||||
{ name: 'tankopedia', merge: (a, b) => lodash.merge(b, a) },
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
Reference in new issue
Block a user