diff --git a/.vscode/settings.json b/.vscode/settings.json index d8fbca767..4b300a69e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,8 @@ "adsense", "aeson", "alphatest", + "analyticsdata", + "analyticsreporting", "APAC", "apcr", "applypatches", diff --git a/bun.lockb b/bun.lockb index 5940e702f..a63dc01fe 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 5a5b7dcb3..71d7a122c 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -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": { diff --git a/packages/website/package.json b/packages/website/package.json index 01a86e424..4794fc8d9 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -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", diff --git a/packages/website/src/components/HomePageHotTanks.tsx b/packages/website/src/components/HomePageHotTanks.tsx index c9e63e339..82dd95771 100644 --- a/packages/website/src/components/HomePageHotTanks.tsx +++ b/packages/website/src/components/HomePageHotTanks.tsx @@ -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 ( - + Popular tanks + + Last 7 days + - {popularTanks.map((tank) => ( - - + {hotTanks.map((row) => ( + + + + {Math.round(row.views * (30 / 7)).toLocaleString('en-US')} + + } + /> ))} diff --git a/packages/website/src/components/Party3.astro b/packages/website/src/components/Party3.astro index e324ab60e..f3730d9dc 100644 --- a/packages/website/src/components/Party3.astro +++ b/packages/website/src/components/Party3.astro @@ -1,8 +1,14 @@ +--- +import { assertSecret } from '@blitzkit/core'; +--- + {/* google analytics */} + src={`https://www.googletagmanager.com/gtag/js?id=${assertSecret( + import.meta.env.PUBLIC_GOOGLE_TAG_MANAGER_ID, + )}`}> {/* nitro ads */} diff --git a/packages/website/src/components/TankCard/index.tsx b/packages/website/src/components/TankCard/index.tsx index 076c2405c..eea103f66 100644 --- a/packages/website/src/components/TankCard/index.tsx +++ b/packages/website/src/components/TankCard/index.tsx @@ -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; }; diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/index.tsx index d2f03b7a2..1e36c0c08 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/index.tsx @@ -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;