feat: real popular tanks list

This commit is contained in:
tresabhi committed 2024-12-12 15:03:51 -06:00
1 parent 31a61c8a6b
commit 4b8a154142
8 files changed
+100 -45

No files matched your search

+2
View File
@@ -7,6 +7,8 @@
"adsense", "adsense",
"aeson", "aeson",
"alphatest", "alphatest",
"analyticsdata",
"analyticsreporting",
"APAC", "APAC",
"apcr", "apcr",
"applypatches", "applypatches",
BIN
View File
Binary file not shown.
-1
View File
@@ -21,7 +21,6 @@
"csv-parse": "^5.5.6", "csv-parse": "^5.5.6",
"decompress": "^4.2.1", "decompress": "^4.2.1",
"fast-xml-parser": "^4.4.1", "fast-xml-parser": "^4.4.1",
"googleapis": "^134.0.0",
"progress": "^2.0.3" "progress": "^2.0.3"
}, },
"devDependencies": { "devDependencies": {
+1
View File
@@ -21,6 +21,7 @@
"@react-three/fiber": "^8.17.10", "@react-three/fiber": "^8.17.10",
"compare-versions": "^6.1.1", "compare-versions": "^6.1.1",
"echarts": "^5.5.1", "echarts": "^5.5.1",
"googleapis": "^144.0.0",
"immer": "^10.1.1", "immer": "^10.1.1",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"markdown-to-jsx": "^7.5.0", "markdown-to-jsx": "^7.5.0",
@@ -1,30 +1,67 @@
import { Box, Flex, Heading } from '@radix-ui/themes'; import { assertSecret } from '@blitzkit/core';
import { awaitableAverageDefinitions } from '../core/awaitables/averageDefinitions'; import { EyeOpenIcon } from '@radix-ui/react-icons';
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
import { google } from 'googleapis';
import { awaitableTankDefinitions } from '../core/awaitables/tankDefinitions'; import { awaitableTankDefinitions } from '../core/awaitables/tankDefinitions';
import { TankopediaPersistent } from '../stores/tankopediaPersistent'; import { TankopediaPersistent } from '../stores/tankopediaPersistent';
import { TankCard } from './TankCard'; import { TankCard } from './TankCard';
const [tankDefinitions, averageDefinitions] = await Promise.all([ const [tankDefinitions] = await Promise.all([awaitableTankDefinitions, ,]);
awaitableTankDefinitions,
awaitableAverageDefinitions,
]);
const tanks = Object.values(tankDefinitions.tanks);
const tankAverages = Object.entries(averageDefinitions.averages).map(
([id, average]) => ({
id: Number(id),
...average,
}),
);
// lol don't ask questions const auth = await google.auth.getClient({
const testTanks = tanks keyFile: import.meta.env.GOOGLE_APPLICATION_CREDENTIALS,
.filter((tank) => tank.testing) scopes: ['https://www.googleapis.com/auth/analytics.readonly'],
.sort((a, b) => b.tier - a.tier); });
const mostPlayedTanks = tankAverages const analytics = google.analyticsdata({ version: 'v1beta', auth });
.sort((a, b) => b.samples.d_1 - a.samples.d_1) const report = await analytics.properties.runReport({
.map(({ id }) => tankDefinitions.tanks[id]) property: `properties/${assertSecret(import.meta.env.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID)}`,
.filter((tank) => tank !== undefined && !tank.testing); requestBody: {
const popularTanks = [...testTanks, ...mostPlayedTanks].slice(0, 8); dateRanges: [{ startDate: '7daysAgo', endDate: 'today' }],
dimensions: [{ name: 'pagePath' }],
metrics: [{ name: 'screenPageViews' }],
orderBys: [{ metric: { metricName: 'screenPageViews' }, desc: true }],
dimensionFilter: {
filter: {
fieldName: 'pagePath',
stringFilter: { matchType: 'BEGINS_WITH', value: '/tools/tankopedia/' },
},
},
},
});
if (!report.data.rows) {
throw new Error('No rows in report');
}
const hotTanks = report.data.rows
.filter(
(row) =>
row.dimensionValues &&
row.dimensionValues[0].value &&
row.dimensionValues[0].value !== '/tools/tankopedia/' &&
row.metricValues &&
row.metricValues[0].value,
)
.map((row) => ({
id: Number(
row.dimensionValues![0].value!.match(
/\/tools\/tankopedia\/(\d+)\/?/,
)?.[1],
),
views: Number(row.metricValues![0].value!),
}))
.filter((row) => {
if (!(row.id in tankDefinitions.tanks)) {
// console.log('Unknown tank', row.id);
}
return row.id in tankDefinitions.tanks;
})
.slice(0, 16)
.map(({ id, views }) => ({
tank: tankDefinitions.tanks[id],
views,
}));
export function HomePageHotTanks() { export function HomePageHotTanks() {
return ( return (
@@ -36,15 +73,26 @@ export function HomePageHotTanks() {
function Content() { function Content() {
return ( return (
<Flex direction="column" gap="5" pt="4" pb="8"> <Flex direction="column" pt="4" pb="8">
<Heading align="center" size="5"> <Heading align="center" size="5">
Popular tanks Popular tanks
</Heading> </Heading>
<Text color="gray" align="center" mb="5" size="2">
Last 7 days
</Text>
<Flex justify="center" gap="4" wrap="wrap"> <Flex justify="center" gap="4" wrap="wrap">
{popularTanks.map((tank) => ( {hotTanks.map((row) => (
<Box width="7rem" key={tank.id}> <Box width="7rem" key={row.tank.id}>
<TankCard tank={tank} /> <TankCard
tank={row.tank}
discriminator={
<Flex align="center" gap="1" justify="center">
<EyeOpenIcon />
{Math.round(row.views * (30 / 7)).toLocaleString('en-US')}
</Flex>
}
/>
</Box> </Box>
))} ))}
</Flex> </Flex>
+8 -2
View File
@@ -1,8 +1,14 @@
---
import { assertSecret } from '@blitzkit/core';
---
{/* google analytics */} {/* google analytics */}
<script <script
is:inline is:inline
async async
src="https://www.googletagmanager.com/gtag/js?id=G-GL2JVHCGPQ"></script> src={`https://www.googletagmanager.com/gtag/js?id=${assertSecret(
import.meta.env.PUBLIC_GOOGLE_TAG_MANAGER_ID,
)}`}></script>
<script is:inline> <script is:inline>
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
@@ -10,7 +16,7 @@
dataLayer.push(arguments); dataLayer.push(arguments);
} }
gtag('js', new Date()); gtag('js', new Date());
gtag('config', 'G-GL2JVHCGPQ'); gtag('config', assertSecret(import.meta.env.PUBLIC_GOOGLE_TAG_MANAGER_ID));
</script> </script>
{/* nitro ads */} {/* nitro ads */}
@@ -1,7 +1,7 @@
import { asset, tankIcon, TankType, type TankDefinition } from '@blitzkit/core'; import { asset, tankIcon, TankType, type TankDefinition } from '@blitzkit/core';
import { Flex, Link, Text, type TextProps } from '@radix-ui/themes'; import { Flex, Link, Text, type TextProps } from '@radix-ui/themes';
import { uniq } from 'lodash-es'; import { uniq } from 'lodash-es';
import { forwardRef } from 'react'; import { forwardRef, type ReactNode } from 'react';
import { TankopediaPersistent } from '../../stores/tankopediaPersistent'; import { TankopediaPersistent } from '../../stores/tankopediaPersistent';
import { classIcons } from '../ClassIcon'; import { classIcons } from '../ClassIcon';
import { MAX_RECENTLY_VIEWED } from '../TankSearch/constants'; import { MAX_RECENTLY_VIEWED } from '../TankSearch/constants';
@@ -10,7 +10,7 @@ import './index.css';
type TankCardProps = TextProps & { type TankCardProps = TextProps & {
tank: TankDefinition; tank: TankDefinition;
onTankSelect?: (tank: TankDefinition) => void; onTankSelect?: (tank: TankDefinition) => void;
discriminator?: string; discriminator: ReactNode;
noLink?: boolean; noLink?: boolean;
}; };
@@ -108,7 +108,6 @@ export function SceneProps() {
let { dispersion } = characteristics; let { dispersion } = characteristics;
const turretRotationSpeed = degToRad(characteristics.turretTraverseSpeed); const turretRotationSpeed = degToRad(characteristics.turretTraverseSpeed);
const gunRotationSpeed = degToRad(characteristics.gunTraverseSpeed); const gunRotationSpeed = degToRad(characteristics.gunTraverseSpeed);
const mockTank = useLoader(GLTFLoader, asset(`3d/tanks/models/12305.glb`)); const mockTank = useLoader(GLTFLoader, asset(`3d/tanks/models/12305.glb`));
const texture = useLoader(TextureLoader, imgur('C28Z8nU')); const texture = useLoader(TextureLoader, imgur('C28Z8nU'));
texture.anisotropy = 2; texture.anisotropy = 2;
@@ -119,6 +118,7 @@ export function SceneProps() {
}); });
let lastTime = 0; let lastTime = 0;
let lastPenalty = 0;
let DEBUG_disable = false; let DEBUG_disable = false;
useFrame(({ raycaster, camera, clock, gl }) => { useFrame(({ raycaster, camera, clock, gl }) => {
@@ -160,22 +160,22 @@ export function SceneProps() {
const desiredYaw = Math.atan2(-direction.x, -direction.z); const desiredYaw = Math.atan2(-direction.x, -direction.z);
const deltaPitch = desiredPitch - lastPitch; const deltaPitch = desiredPitch - lastPitch;
const deltaYaw = desiredYaw - lastYaw; const deltaYaw = desiredYaw - lastYaw;
const maxDeltaPitch = const deltaAngle = Math.sqrt(deltaPitch ** 2 + deltaYaw ** 2);
Math.sign(deltaPitch) * const clampedDeltaAngle = Math.min(gunRotationSpeed * deltaT, deltaAngle);
Math.min(gunRotationSpeed * deltaT, Math.abs(deltaPitch)); const clampedDeltaPitch = deltaPitch * (deltaAngle / clampedDeltaAngle);
const maxDeltaYaw = const clampedDeltaYaw = deltaYaw * (deltaAngle / clampedDeltaAngle);
Math.sign(deltaYaw) *
Math.min(turretRotationSpeed * deltaT, Math.abs(deltaYaw));
const [pitch, yaw] = applyPitchYawLimits( const [pitch, yaw] = applyPitchYawLimits(
lastPitch + maxDeltaPitch, lastPitch + clampedDeltaPitch,
lastYaw + maxDeltaYaw, lastYaw + clampedDeltaYaw,
gunModelDefinition.pitch, gunModelDefinition.pitch,
turretModelDefinition.yaw, turretModelDefinition.yaw,
); );
const deltaRotation = Math.abs(maxDeltaPitch) + Math.abs(maxDeltaYaw); const actualDeltaPitch = pitch - lastPitch;
const rotationSpeed = deltaRotation; const actualDeltaYaw = yaw - lastYaw;
const deltaRotation = Math.abs(actualDeltaPitch) + Math.abs(actualDeltaYaw);
const rotationSpeed = deltaRotation / deltaT;
const turretTraversePenalty = const turretTraversePenalty =
characteristics.dispersionTurretTraversing * radToDeg(rotationSpeed); characteristics.dispersionTurretTraversing * radToDeg(rotationSpeed);
@@ -186,9 +186,8 @@ export function SceneProps() {
dispersionMod * Math.exp(-deltaT / characteristics.aimTime) - dispersionMod * Math.exp(-deltaT / characteristics.aimTime) -
dispersionMod; dispersionMod;
dispersion += penalty + dispersionModDischarged; dispersion += penalty - lastPenalty + dispersionModDischarged;
lastPenalty = penalty;
document.title = `${dispersion.toFixed(3)} (${penalty.toFixed(3)})`;
modelTransformEvent.emit({ pitch, yaw }); modelTransformEvent.emit({ pitch, yaw });
lastPitch = pitch; lastPitch = pitch;