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