Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8478930f2e | ||
|
|
6a74451740 | ||
|
|
09f5d4e3f7 | ||
|
|
0365656a0e | ||
|
|
90a46eeffa | ||
|
|
1249877a8c | ||
|
|
02df1e59d8 | ||
|
|
edce64c1bd | ||
|
|
74b821ac13 | ||
|
|
a5a72f9536 | ||
|
|
fe3d029e74 | ||
|
|
a69ea38947 | ||
|
|
bc9f7054e1 | ||
|
|
53c2f9605f | ||
|
|
95f67a04d7 | ||
|
|
1e6ee2b80d | ||
|
|
6a21c9aaac | ||
|
|
7e11c1e5ce | ||
|
|
2dc4f12246 | ||
|
|
354aca5057 | ||
|
|
c5f156eecc | ||
|
|
37b89d1f2d | ||
|
|
74c358dde5 | ||
|
|
4362746de3 | ||
|
|
d5d97e772a | ||
|
|
654c87dae7 | ||
|
|
e94f8a7cd2 | ||
|
|
d39ffecb3b | ||
|
|
6f8e12657c | ||
|
|
4a79f98f3f | ||
|
|
06de86c5a6 | ||
|
|
37ba6d37d4 | ||
|
|
636add3a18 | ||
|
|
2e13bcfee2 | ||
|
|
af53713c60 | ||
|
|
c37820d2cd | ||
|
|
d51b6bdd18 | ||
|
|
67ce4815df | ||
|
|
8c14c3d093 | ||
|
|
8373fb1b7a | ||
|
|
3aafe4d9f1 | ||
|
|
2c2c3687c7 | ||
|
|
9375f134db | ||
|
|
1eca71d28c | ||
|
|
9bf1554c30 | ||
|
|
f48c5d246d | ||
|
|
5e9aa99755 | ||
|
|
e7386ff766 | ||
|
|
4129a1964f | ||
|
|
64d12bba8b | ||
|
|
69b13fd33a | ||
|
|
bc6bb4f10e | ||
|
|
5adf90bab2 | ||
|
|
bc8cfc9897 | ||
|
|
9037e2caff |
No files matched your search
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"indentSize": 2
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
name: Build Assets
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: main
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
cdn_branch:
|
||||
type: string
|
||||
required: false
|
||||
default: main
|
||||
description: What CDN branch to write to
|
||||
zip_url:
|
||||
type: string
|
||||
required: false
|
||||
description: ZIP URL to download instead of Steam client
|
||||
zip_root:
|
||||
type: string
|
||||
required: false
|
||||
description: Root dir of ZIP which houses the executable and Data dir
|
||||
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.cdn_branch }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }}
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
||||
WOTB_DLC_CDN: ${{ secrets.WOTB_DLC_CDN }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
PUBLIC_ASSET_REPO: blitzkit/assets
|
||||
PUBLIC_ASSET_BRANCH: main
|
||||
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
|
||||
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
|
||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
||||
ALLOW_ROBOTS: true
|
||||
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
|
||||
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
|
||||
PUBLIC_PATREON_CLIENT_ID: ${{ secrets.PUBLIC_PATREON_CLIENT_ID }}
|
||||
PUBLIC_PATREON_REDIRECT_URI: ${{ secrets.PUBLIC_PATREON_REDIRECT_URI }}
|
||||
PATREON_CLIENT_SECRET: ${{ secrets.PATREON_CLIENT_SECRET }}
|
||||
PUBLIC_NITROPAY_ID: ${{ secrets.PUBLIC_NITROPAY_ID }}
|
||||
PUBLIC_DEBUG_MISSING_I18N: false
|
||||
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
|
||||
PUBLIC_ASSET_FAST_USER: blitzkit
|
||||
PUBLIC_ASSET_FAST_REPO: assets
|
||||
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
|
||||
|
||||
jobs:
|
||||
download_client:
|
||||
name: Download client
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: (ZIP) Maximize build space
|
||||
if: github.event.inputs.zip_url != ''
|
||||
uses: easimon/maximize-build-space@master
|
||||
with:
|
||||
root-reserve-mb: 512
|
||||
swap-size-mb: 1024
|
||||
remove-dotnet: 'true'
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: (Steam) Setup bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
if: github.event.inputs.zip_url == ''
|
||||
|
||||
- name: (Steam) Install dependencies
|
||||
if: github.event.inputs.zip_url == ''
|
||||
run: bun install
|
||||
|
||||
- name: (Steam) Setup DepotDownloader
|
||||
if: github.event.inputs.zip_url == ''
|
||||
run: bun run setup:depotdownloader
|
||||
|
||||
- name: (Steam) Download client
|
||||
if: github.event.inputs.zip_url == ''
|
||||
run: |
|
||||
chmod +x ./temp/depotDownloader/DepotDownloader
|
||||
./temp/depotDownloader/DepotDownloader -q -os windows -app 444200 -username ${{ secrets.DEPOT_DOWNLOADER_USERNAME }} -password "${{ secrets.DEPOT_DOWNLOADER_PASSWORD }}"
|
||||
|
||||
- name: (Steam) Apply patches
|
||||
if: github.event.inputs.zip_url == ''
|
||||
run: bun setup:patches --depot
|
||||
|
||||
- name: (ZIP) Download client
|
||||
if: github.event.inputs.zip_url != ''
|
||||
env:
|
||||
ZIP_URL: ${{ github.event.inputs.zip_url }}
|
||||
ZIP_ROOT: ${{ github.event.inputs.zip_root }}
|
||||
run: |
|
||||
wget -q -O client.zip $ZIP_URL
|
||||
unzip -q client.zip -d client
|
||||
rm client.zip
|
||||
mkdir -p depots/444202/0
|
||||
mv client/$ZIP_ROOT/* depots/444202/0
|
||||
|
||||
- name: Archive depots
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: depots
|
||||
path: depots
|
||||
|
||||
build_assets:
|
||||
name: Build assets
|
||||
runs-on: ubuntu-latest
|
||||
needs: download_client
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Setup bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Lint
|
||||
run: bun run lint:website
|
||||
|
||||
- name: Download depots
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: depots
|
||||
path: depots
|
||||
|
||||
- name: AppArmor hack
|
||||
run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
|
||||
|
||||
- name: Start server and build assets
|
||||
run: |
|
||||
bun dev:website --verbosepa &
|
||||
SERVER_PID=$!
|
||||
|
||||
bun run build:assets --all-targets --depot --production
|
||||
|
||||
kill $SERVER_PID || true
|
||||
@@ -1,49 +0,0 @@
|
||||
name: Averages
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 8 * * *'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
|
||||
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
|
||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
||||
ALLOW_ROBOTS: 'true'
|
||||
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
||||
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
|
||||
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
|
||||
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
|
||||
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
|
||||
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
|
||||
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Build averages
|
||||
env:
|
||||
PUBLIC_ASSET_BRANCH: main
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }}
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||
run: bun run build:averages --production
|
||||
@@ -1,32 +0,0 @@
|
||||
name: Discover IDs
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * 1'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Discover IDs
|
||||
run: bun build:ids --production
|
||||
@@ -1,41 +0,0 @@
|
||||
name: Rating Asia
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 17 * * *'
|
||||
|
||||
jobs:
|
||||
host:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Generate leaderboard
|
||||
env:
|
||||
PUBLIC_ASSET_BRANCH: main
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
|
||||
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
|
||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
||||
ALLOW_ROBOTS: 'true'
|
||||
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
||||
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
|
||||
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
|
||||
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
|
||||
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
|
||||
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
|
||||
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
|
||||
run: bun build:ratingleaderboard --region=asia
|
||||
@@ -1,41 +0,0 @@
|
||||
name: Rating Europe
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 23 * * *'
|
||||
|
||||
jobs:
|
||||
host:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Generate leaderboard
|
||||
env:
|
||||
PUBLIC_ASSET_BRANCH: main
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
|
||||
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
|
||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
||||
ALLOW_ROBOTS: 'true'
|
||||
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
||||
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
|
||||
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
|
||||
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
|
||||
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
|
||||
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
|
||||
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
|
||||
run: bun build:ratingleaderboard --region=eu
|
||||
@@ -1,41 +0,0 @@
|
||||
name: Rating North America
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 5 * * *'
|
||||
|
||||
jobs:
|
||||
host:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Generate leaderboard
|
||||
env:
|
||||
PUBLIC_ASSET_BRANCH: main
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
|
||||
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
|
||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
||||
ALLOW_ROBOTS: 'true'
|
||||
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
||||
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
|
||||
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
|
||||
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
|
||||
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
|
||||
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
|
||||
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
|
||||
run: bun build:ratingleaderboard --region=com
|
||||
@@ -17,31 +17,34 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
env:
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
||||
ALLOW_ROBOTS: ${{ secrets.ALLOW_ROBOTS }}
|
||||
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
||||
AUTO_PROXY_ORIGIN: ${{ secrets.AUTO_PROXY_ORIGIN }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
|
||||
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
|
||||
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
|
||||
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
|
||||
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
|
||||
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- uses: actions/configure-pages@v5
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- name: Configure pages
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
@@ -55,13 +58,12 @@ jobs:
|
||||
path: dist/website
|
||||
|
||||
deploy-github-pages:
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
name: Deploy to GitHub Pages
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
environment:
|
||||
name: Production
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
if: >
|
||||
(github.repository == 'tresabhi/blitzkit' && github.ref == 'refs/heads/main') ||
|
||||
(github.repository == 'blitzkit/host-redux' && github.ref == 'refs/heads/ue4')
|
||||
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
|
||||
@@ -22,9 +22,232 @@ depots
|
||||
public/sitemap*
|
||||
/firebase.json
|
||||
target
|
||||
# packages/core/src/protos/*.ts
|
||||
/.vercel
|
||||
/.astro
|
||||
.vercel
|
||||
/src
|
||||
/packages/bot/src/prisma
|
||||
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.userprefs
|
||||
mono_crash.*
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
[Aa][Rr][Mm]64[Ee][Cc]/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
.vs/
|
||||
Generated\ Files/
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
BenchmarkDotNet.Artifacts/
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
ScaffoldingReadMe.txt
|
||||
StyleCopReport.xml
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.idb
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
!Directory.Build.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
_Chutzpah*
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
*.e2e
|
||||
$tf/
|
||||
*.gpState
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
_TeamCity*
|
||||
*.dotCover
|
||||
.axoCover/*
|
||||
|
||||
|
||||
!.axoCover/settings.json
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
_NCrunch_*
|
||||
.NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
.sass-cache/
|
||||
[Ee]xpress/
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
publish/
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
PublishScripts/
|
||||
|
||||
*.nupkg
|
||||
*.snupkg
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
csx/
|
||||
|
||||
*.build.csdef
|
||||
ecf/
|
||||
rcf/
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
*.[Cc]ache
|
||||
!?*.[Cc]ache/
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
Generated_Code/
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
FakesAssemblies/
|
||||
*.GhostDoc.xml
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
*.plg
|
||||
*.opt
|
||||
*.vbw
|
||||
*.vbp
|
||||
*.dsw
|
||||
*.dsp
|
||||
*.ncb
|
||||
*.aps
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
.fake/
|
||||
.cr/personal
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
*.tss
|
||||
*.jmconfig
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
OpenCover/
|
||||
ASALocalRun/
|
||||
*.binlog
|
||||
.aws-sam
|
||||
*.nvuser
|
||||
.mfractor/
|
||||
.localhistory/
|
||||
.vshistory/
|
||||
healthchecksdb
|
||||
MigrationBackup/
|
||||
.ionide/
|
||||
FodyWeavers.xsd
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
.history/
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
*.sln.iml
|
||||
@@ -51,6 +51,7 @@
|
||||
"CUBEMAP",
|
||||
"CUBETEXCOORD",
|
||||
"customperiod",
|
||||
"Datacard",
|
||||
"datasource",
|
||||
"Dava",
|
||||
"dbid",
|
||||
@@ -68,6 +69,7 @@
|
||||
"DIDS",
|
||||
"donotremove",
|
||||
"drei",
|
||||
"dsapp",
|
||||
"dvpd",
|
||||
"dvpl",
|
||||
"DVPLCRC",
|
||||
@@ -129,6 +131,7 @@
|
||||
"noout",
|
||||
"normalmap",
|
||||
"oneof",
|
||||
"oodle",
|
||||
"oopsies",
|
||||
"Oopsy",
|
||||
"opentest",
|
||||
@@ -193,6 +196,8 @@
|
||||
"Turretless",
|
||||
"TYPELESS",
|
||||
"ubjson",
|
||||
"Uncompiled",
|
||||
"undiscriminated",
|
||||
"Unicum",
|
||||
"UNLOCALIZED",
|
||||
"UNORM",
|
||||
@@ -228,5 +233,6 @@
|
||||
"deno.unstable": false,
|
||||
"cSpell.enableFiletypes": ["glsl", "proto3"],
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||
"liveServer.settings.multiRootWorkspaceName": "blitzkit"
|
||||
"liveServer.settings.multiRootWorkspaceName": "blitzkit",
|
||||
"git.ignoreLimitWarning": true
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/BlitzKit.sln",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"publish",
|
||||
"${workspaceFolder}/BlitzKit.sln",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"watch",
|
||||
"run",
|
||||
"--project",
|
||||
"${workspaceFolder}/BlitzKit.sln"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F23F76D4-C350-4BBA-9DF1-E67C5CD391B1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlitzKit.FFI", "src\BlitzKit.FFI\BlitzKit.FFI.csproj", "{8E798C66-C863-4A67-87EA-853109EEE037}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{8E798C66-C863-4A67-87EA-853109EEE037}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8E798C66-C863-4A67-87EA-853109EEE037}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8E798C66-C863-4A67-87EA-853109EEE037}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8E798C66-C863-4A67-87EA-853109EEE037}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{8E798C66-C863-4A67-87EA-853109EEE037} = {F23F76D4-C350-4BBA-9DF1-E67C5CD391B1}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -11,3 +11,12 @@ Your premier source for all things Blitz.
|
||||
Go to [application directory for BlitzKit](https://discord.com/application-directory/1097673957865443370) and click "Add to Server." Voila! You're ready to go.
|
||||
|
||||

|
||||
|
||||
## Naming Guide
|
||||
|
||||
| Type | Style | Example |
|
||||
| --------- | -------------- | ---------------------------------------------------------------- |
|
||||
| In-text | CamelCase | BlitzKit |
|
||||
| Code | lowerCamelCase | `let blitzkit = { ... }`, `function fetchBlitzkitData() { ... }` |
|
||||
| URLs | kebab-case | `https://example.com/blitzkit/` |
|
||||
| Constants | SNAKE_CASE | `const BLITZKIT_VERSION = "1.0.0"` |
|
||||
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"name": "blitzkit",
|
||||
"private": true,
|
||||
"version": "1.27.11",
|
||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||
"type": "module",
|
||||
"types": "./types.d.ts",
|
||||
"workspaces": ["packages/*", "submodules/*"],
|
||||
"scripts": {
|
||||
"build:assets": "cd packages/scripts && bun run build:assets",
|
||||
"build:averages": "bun --filter=@blitzkit/scripts run build:averages",
|
||||
"build:bot": "cd packages/scripts && bun run build:bot",
|
||||
"build:website": "bun build:prisma --no-hints && cd packages/website && bun run build",
|
||||
"build:prisma": "prisma generate",
|
||||
"build:sitemap": "bun scripts/sitemap.ts",
|
||||
"build:docs": "vitepress build docs",
|
||||
"build:ratingleaderboard": "cd packages/scripts && bun run build:ratingleaderboard",
|
||||
"dev:bot": "bun run build:bot --dev && bun start:bot",
|
||||
"start:bot": "bun dist/bot/main.js",
|
||||
"dev:docs": "vitepress dev docs",
|
||||
"dev:website": "cd packages/website-ue && bun run dev",
|
||||
"dev:api": "bun --filter=@blitzkit/api run dev",
|
||||
"setup:depotdownloader": "bun --filter=@blitzkit/scripts run setup:depotdownloader",
|
||||
"setup:patches": "bun --filter=@blitzkit/scripts run setup:patches",
|
||||
"start:website": "bun --filter=@blitzkit/website run start",
|
||||
"host:docs": "vitepress preview docs",
|
||||
"lint": "tsc",
|
||||
"lint:website": "cd packages/website && bun run lint",
|
||||
"format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\"",
|
||||
"update": "yarn upgrade --latest",
|
||||
"tunnel": "cloudflared tunnel --url http://localhost:3000",
|
||||
"discoverids": "scripts/discoverIds.ts",
|
||||
"build:protos": "bun packages/scripts/src/protos.ts",
|
||||
"preview:website": "cd packages/website && bun run preview",
|
||||
"build:reviews": "cd packages/scripts && bun build:reviews",
|
||||
"build:ids": "cd packages/scripts && bun build:ids",
|
||||
"prunestrings": "cd packages/scripts && bun prunestrings"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"satori": "^0.10.14",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"discord.js": "^14.16.3",
|
||||
"bepaint": "^0.1.0-beta.3",
|
||||
"markdown-escape": "^2.0.0",
|
||||
"@types/markdown-escape": "^1.1.3",
|
||||
"@types/adm-zip": "^0.5.5",
|
||||
"@types/lz4js": "^0.2.1",
|
||||
"@octokit/plugin-throttling": "^8.2.0",
|
||||
"@octokit/rest": "^20.1.1",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"crc": "^4.3.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"lz4js": "^0.2.0",
|
||||
"yaml": "^2.5.0",
|
||||
"ts-deepmerge": "^7.0.2",
|
||||
"@types/decompress": "^4.2.7",
|
||||
"@types/progress": "^2.0.7",
|
||||
"@gltf-transform/core": "^4.0.10",
|
||||
"@gltf-transform/extensions": "^4.0.10",
|
||||
"@gltf-transform/functions": "^4.0.10",
|
||||
"csv-parse": "^5.5.6",
|
||||
"decompress": "^4.2.1",
|
||||
"esbuild": "^0.19.12",
|
||||
"fast-xml-parser": "^4.4.1",
|
||||
"progress": "^2.0.3",
|
||||
"puppeteer": "^23.10.4",
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@playform/compress": "^0.1.9",
|
||||
"@playform/inline": "^0.1.2",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"astro-compressor": "^1.0.0",
|
||||
"@fontsource/inter": "^5.2.5",
|
||||
"@nanostores/persistent": "^0.10.2",
|
||||
"@nanostores/react": "^0.7.3",
|
||||
"@nivo/bar": "^0.87.0",
|
||||
"@nivo/line": "^0.88.0",
|
||||
"compare-versions": "^6.1.1",
|
||||
"echarts": "^5.5.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"markdown-to-jsx": "^7.5.0",
|
||||
"nanostores": "^0.11.3",
|
||||
"react-device-detect": "^2.2.3",
|
||||
"react-promise-suspense": "^0.3.4",
|
||||
"urlon": "^3.1.0",
|
||||
"@types/bun": "latest",
|
||||
"@types/node": "^22.14.1",
|
||||
"prisma": "^6.6.0",
|
||||
"typescript": "^5.8.3",
|
||||
"googleapis": "^144.0.0",
|
||||
"protobufjs": "^7.5.2",
|
||||
"quicklime": "^1.2.0",
|
||||
"sharp": "^0.33.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"name": "blitzkit",
|
||||
"private": true,
|
||||
"version": "1.27.11",
|
||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||
"type": "module",
|
||||
"types": "./types.d.ts",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"submodules/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build:assets": "cd packages/scripts && bun run build:assets",
|
||||
"build:averages": "bun --filter=@blitzkit/scripts run build:averages",
|
||||
"build:bot": "cd packages/scripts && bun run build:bot",
|
||||
"build:website": "bun build:prisma --no-hints && cd packages/website && bun run build",
|
||||
"build:prisma": "prisma generate",
|
||||
"build:sitemap": "bun scripts/sitemap.ts",
|
||||
"build:docs": "vitepress build docs",
|
||||
"build:ratingleaderboard": "cd packages/scripts && bun run build:ratingleaderboard",
|
||||
"dev:bot": "bun run build:bot --dev && bun start:bot",
|
||||
"start:bot": "bun dist/bot/main.js",
|
||||
"dev:docs": "vitepress dev docs",
|
||||
"dev:website": "cd packages/website-ue && bun run dev",
|
||||
"dev:api": "bun --filter=@blitzkit/api run dev",
|
||||
"setup:depotdownloader": "bun --filter=@blitzkit/scripts run setup:depotdownloader",
|
||||
"setup:patches": "bun --filter=@blitzkit/scripts run setup:patches",
|
||||
"start:website": "bun --filter=@blitzkit/website run start",
|
||||
"host:docs": "vitepress preview docs",
|
||||
"lint": "tsc",
|
||||
"lint:website": "cd packages/website && bun run lint",
|
||||
"format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\"",
|
||||
"update": "yarn upgrade --latest",
|
||||
"tunnel": "cloudflared tunnel --url http://localhost:3000",
|
||||
"discoverids": "scripts/discoverIds.ts",
|
||||
"build:protos": "bun packages/scripts/src/protos.ts",
|
||||
"preview:website": "cd packages/website && bun run preview",
|
||||
"build:reviews": "cd packages/scripts && bun build:reviews",
|
||||
"build:ids": "cd packages/scripts && bun build:ids",
|
||||
"prunestrings": "cd packages/scripts && bun prunestrings"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"satori": "^0.10.14",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"discord.js": "^14.16.3",
|
||||
"fuzzysort": "^2.0.4",
|
||||
"bepaint": "^0.1.0-beta.3",
|
||||
"markdown-escape": "^2.0.0",
|
||||
"@types/markdown-escape": "^1.1.3",
|
||||
"@types/adm-zip": "^0.5.5",
|
||||
"@types/lz4js": "^0.2.1",
|
||||
"@octokit/plugin-throttling": "^8.2.0",
|
||||
"@octokit/rest": "^20.1.1",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"crc": "^4.3.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"lz4js": "^0.2.0",
|
||||
"ts-proto": "^2.2.2",
|
||||
"yaml": "^2.5.0",
|
||||
"ts-deepmerge": "^7.0.2",
|
||||
"@types/decompress": "^4.2.7",
|
||||
"@types/progress": "^2.0.7",
|
||||
"@gltf-transform/core": "^4.0.10",
|
||||
"@gltf-transform/extensions": "^4.0.10",
|
||||
"@gltf-transform/functions": "^4.0.10",
|
||||
"csv-parse": "^5.5.6",
|
||||
"decompress": "^4.2.1",
|
||||
"esbuild": "^0.19.12",
|
||||
"fast-xml-parser": "^4.4.1",
|
||||
"progress": "^2.0.3",
|
||||
"puppeteer": "^23.10.4",
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"@playform/compress": "^0.1.9",
|
||||
"@playform/inline": "^0.1.2",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/three": "^0.160.0",
|
||||
"astro-compressor": "^1.0.0",
|
||||
"@astrojs/partytown": "^2.1.4",
|
||||
"@astrolib/seo": "^1.0.0-beta.8",
|
||||
"@fontsource/inter": "^5.2.5",
|
||||
"@nanostores/persistent": "^0.10.2",
|
||||
"@nanostores/react": "^0.7.3",
|
||||
"@nivo/bar": "^0.87.0",
|
||||
"@nivo/line": "^0.88.0",
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/themes": "^3.1.4",
|
||||
"@react-three/drei": "^9.115.0",
|
||||
"@react-three/fiber": "^8.17.10",
|
||||
"compare-versions": "^6.1.1",
|
||||
"echarts": "^5.5.1",
|
||||
"immer": "^10.1.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"markdown-to-jsx": "^7.5.0",
|
||||
"nanostores": "^0.11.3",
|
||||
"react-device-detect": "^2.2.3",
|
||||
"react-promise-suspense": "^0.3.4",
|
||||
"three": "^0.169.0",
|
||||
"urlon": "^3.1.0",
|
||||
"zustand": "^4.5.5",
|
||||
"@types/bun": "latest",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^22.14.1",
|
||||
"prettier": "^3.5.3",
|
||||
"prisma": "^6.6.0",
|
||||
"typescript": "^5.8.3",
|
||||
"googleapis": "^144.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"protobufjs": "^7.5.2",
|
||||
"quicklime": "^1.2.0",
|
||||
"sharp": "^0.33.5",
|
||||
"@blitzkit/core": "workspace:*",
|
||||
"@blitzkit/i18n": "workspace:*",
|
||||
"@astrojs/react": "^4.3.0",
|
||||
"@types/react": "^19.1.5",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"astro": "^5.8.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,14 @@
|
||||
"type": "module",
|
||||
"types": "./types.d.ts",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
"packages/*",
|
||||
"submodules/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build:assets": "cd packages/scripts && bun run build:assets",
|
||||
"build:averages": "bun --filter=@blitzkit/scripts run build:averages",
|
||||
"build:bot": "cd packages/scripts && bun run build:bot",
|
||||
"build:website": "bun build:prisma --no-hints && cd packages/website && bun run build",
|
||||
"build:website": "cd packages/website-ue && bun run build",
|
||||
"build:prisma": "prisma generate",
|
||||
"build:sitemap": "bun scripts/sitemap.ts",
|
||||
"build:docs": "vitepress build docs",
|
||||
@@ -30,29 +31,21 @@
|
||||
"lint": "tsc",
|
||||
"lint:website": "cd packages/website && bun run lint",
|
||||
"format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\"",
|
||||
"update": "yarn upgrade --latest",
|
||||
"update": "yarn upgace --latest",
|
||||
"tunnel": "cloudflared tunnel --url http://localhost:3000",
|
||||
"discoverids": "scripts/discoverIds.ts",
|
||||
"build:protos": "bun packages/scripts/src/protos.ts",
|
||||
"preview:website": "cd packages/website && bun run preview",
|
||||
"build:reviews": "cd packages/scripts && bun build:reviews",
|
||||
"build:ids": "cd packages/scripts && bun build:ids",
|
||||
"prunestrings": "cd packages/scripts && bun prunestrings"
|
||||
"prunestrings": "cd packages/scripts && bun prunestrings",
|
||||
"build:ffi": "bun build:windows:ffi",
|
||||
"build:windows:ffi": "dotnet publish src/BlitzKit.FFI -r win-x64 /p:PublishAot=true",
|
||||
"build:linux:ffi": "dotnet publish src/BlitzKit.FFI -r linux-x64 /p:PublishAot=true"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"googleapis": "^144.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"protobufjs": "^7.5.2",
|
||||
"quicklime": "^1.2.0",
|
||||
"sharp": "^0.33.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^22.14.1",
|
||||
"prettier": "^3.5.3",
|
||||
"prisma": "^6.6.0",
|
||||
"typescript": "^5.8.3"
|
||||
"@types/bun": "^1.2.15",
|
||||
"prettier": "^3.5.3"
|
||||
}
|
||||
}
|
||||
@@ -3,24 +3,8 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"types": "src/types.d.ts",
|
||||
"dependencies": {
|
||||
"@blitzkit/core": "workspace:*",
|
||||
"@blitzkit/i18n": "workspace:*",
|
||||
"satori": "^0.10.14",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"discord.js": "^14.16.3",
|
||||
"react": "^18.3.1",
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"fuzzysort": "^2.0.4",
|
||||
"bepaint": "^0.1.0-beta.3",
|
||||
"dotenv": "^16.4.5",
|
||||
"markdown-escape": "^2.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "bun --watch src/main.ts",
|
||||
"start": "bun src/main.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/markdown-escape": "^1.1.3"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src/**/*", "../core/src/blitzkit/supportedLocales.ts", "../i18n/src/unwrapper.ts"]
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"../core/src/blitzkit/supportedLocales.ts",
|
||||
"../i18n/src/unwrapper.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../../"
|
||||
}
|
||||
}
|
||||
@@ -4,20 +4,9 @@
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"dependencies": {
|
||||
"@octokit/plugin-throttling": "^8.2.0",
|
||||
"@octokit/rest": "^20.1.1",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"crc": "^4.3.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"lz4js": "^0.2.0",
|
||||
"pickleparser": "^0.2.1",
|
||||
"protobufjs": "^7.4.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"js-yaml": "^4.1.0",
|
||||
"ts-proto": "^2.2.2",
|
||||
"yaml": "^2.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/adm-zip": "^0.5.5",
|
||||
"@types/lz4js": "^0.2.1"
|
||||
"@blitzkit/closed": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { CatalogItem } from '@protos/blitz_items';
|
||||
import { Any } from '@protos/google/protobuf/any';
|
||||
import { lowerFirst } from 'lodash-es';
|
||||
import { compareUint8Arrays } from '../../../scripts/src/core/github/compareUint8Arrays';
|
||||
import { MessageFns } from '../protos';
|
||||
|
||||
export class CatalogItemAccessor {
|
||||
public id: string;
|
||||
components: Record<string, Any> = {};
|
||||
count: number;
|
||||
|
||||
constructor(private item: CatalogItem) {
|
||||
this.id = item.catalog_id;
|
||||
this.count = item.components.length;
|
||||
|
||||
for (const component of item.components) {
|
||||
if (
|
||||
component.value === undefined ||
|
||||
component.value.component === undefined
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.components[component.key] = component.value.component;
|
||||
}
|
||||
}
|
||||
|
||||
undiscriminatedId() {
|
||||
return this.id.split('.').slice(1).join('.');
|
||||
}
|
||||
|
||||
static fromComponent(id: string, component: Any) {
|
||||
return this.fromComponents(id, [component]);
|
||||
}
|
||||
|
||||
static fromComponents(id: string, components: Any[]) {
|
||||
return new CatalogItemAccessor({
|
||||
catalog_id: id,
|
||||
components: components.map((component) => ({
|
||||
key: lowerFirst(
|
||||
component.type_url.slice(2).split('.').slice(1).join('.'),
|
||||
),
|
||||
value: { type_url: component.type_url, component },
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
addComponent(key: string, value: Any) {
|
||||
this.components[key] = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
getOptional<Type>(Message: MessageFns<Type>, component: string) {
|
||||
if (component in this.components) {
|
||||
return Message.decode(this.components[component].value);
|
||||
}
|
||||
}
|
||||
|
||||
get<Type>(Message: MessageFns<Type>, component: string) {
|
||||
const value = this.getOptional(Message, component);
|
||||
if (value === undefined) throw new Error(`Unknown component: ${component}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
pack() {
|
||||
const item: CatalogItem = { catalog_id: this.id, components: [] };
|
||||
|
||||
for (const componentId in this.components) {
|
||||
const component = this.components[componentId];
|
||||
|
||||
item.components.push({
|
||||
key: componentId,
|
||||
value: { type_url: component.type_url, component },
|
||||
});
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
encode() {
|
||||
return CatalogItem.encode(this.pack()).finish();
|
||||
}
|
||||
|
||||
equals(other: CatalogItemAccessor) {
|
||||
return (
|
||||
this.count === other.count &&
|
||||
this.item.components.every(
|
||||
(component) =>
|
||||
component.value === undefined ||
|
||||
(component.key in other.components &&
|
||||
(component.value.component === undefined ||
|
||||
compareUint8Arrays(
|
||||
other.components[component.key].value,
|
||||
component.value.component.value,
|
||||
))),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ export * from './getTankStats';
|
||||
export * from './idToRegion';
|
||||
export * from './isOnGoingRatingSeason';
|
||||
export * from './isValidBlitzId';
|
||||
export * from './metadata';
|
||||
export * from './nations';
|
||||
export * from './normalizeExtra';
|
||||
export * from './normalizeIds';
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
export class SocketMetadataAccessor {
|
||||
items: Record<string, CatalogItemAccessor> = {};
|
||||
|
||||
constructor(catalogItems: CatalogItem[]) {
|
||||
for (const item of catalogItems) {
|
||||
if (item.catalog_id in this.items) {
|
||||
throw new Error(`Duplicate catalog ID: ${item.catalog_id}`);
|
||||
}
|
||||
|
||||
this.items[item.catalog_id] = new CatalogItemAccessor(item);
|
||||
}
|
||||
}
|
||||
|
||||
get(item: string) {
|
||||
if (item in this.items) return this.items[item];
|
||||
throw new Error(`Unknown catalog ID: ${item}`);
|
||||
}
|
||||
|
||||
add(item: CatalogItemAccessor) {
|
||||
this.items[item.id] = item;
|
||||
return this;
|
||||
}
|
||||
|
||||
getAllTanks() {
|
||||
return this.get('CatalogItemsGroupEntity.all_tanks').get(
|
||||
CatalogItemsComponent,
|
||||
'catalogItemsGroupComponent',
|
||||
);
|
||||
}
|
||||
|
||||
injectBlitzkit() {
|
||||
this.injectBlitzKitTankComponents();
|
||||
this.injectBlitzKitAllTanksEntity();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
injectBlitzKitTankComponents() {
|
||||
for (const item of this.getAllTanks().catalog_items) {
|
||||
const tank = this.get(item);
|
||||
const value = BlitzKitTankCatalogComponent.encode({
|
||||
id: 'TEST',
|
||||
set: 'TEST',
|
||||
}).finish();
|
||||
|
||||
tank.addComponent('blitzkitTankCatalogComponent', {
|
||||
type_url:
|
||||
'./blitzkit_static_tank_component.BlitzKitTankCatalogComponent',
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
injectBlitzKitAllTanksEntity() {
|
||||
const allTanksEntities: BlitzkitAllTanksComponent = { tanks: [] };
|
||||
|
||||
for (const item of this.getAllTanks().catalog_items) {
|
||||
allTanksEntities.tanks.push(this.get(item).pack());
|
||||
}
|
||||
|
||||
this.add(
|
||||
CatalogItemAccessor.fromComponent(
|
||||
'BlitzKitAllTanksEntity.blitzkit_all_tanks',
|
||||
{
|
||||
type_url:
|
||||
'./blitzkit_static_all_tanks_component.BlitzkitAllTanksComponent',
|
||||
value: BlitzkitAllTanksComponent.encode(allTanksEntities).finish(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoteMetadataAccessor {
|
||||
constructor(private base: string) {}
|
||||
|
||||
async get(item: string) {
|
||||
const response = await fetch(`${this.base}/${item}.pb`);
|
||||
|
||||
if (!response.ok) throw new Error(`Unknown catalog ID: ${item}`);
|
||||
|
||||
const buffer = await response.arrayBuffer();
|
||||
const catalogItem = CatalogItem.decode(new Uint8Array(buffer));
|
||||
|
||||
return new CatalogItemAccessor(catalogItem);
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,131 @@
|
||||
import { CatalogItem } from '@protos/blitz_items';
|
||||
import { Metadata } from '@protos/blitz_metadata';
|
||||
import { LocalizationResourcesComponent } from '@protos/blitz_static_localization_resources_component';
|
||||
import { RemoteStorageComponent } from '@protos/blitz_static_remote_storage_component';
|
||||
import { load } from 'js-yaml';
|
||||
import { minimatch } from 'minimatch';
|
||||
import { CatalogItemAccessor } from 'packages/core/src/blitz/catalogItemAccessor';
|
||||
|
||||
interface LocalizationConfig {
|
||||
namespaces: string[];
|
||||
}
|
||||
|
||||
export class MetadataAccessor {
|
||||
items: Record<string, CatalogItem> = {};
|
||||
|
||||
constructor(metadata: Metadata) {
|
||||
for (const item of metadata.catalog_items) {
|
||||
this.items[item.catalog_id] = item;
|
||||
}
|
||||
}
|
||||
|
||||
get(item: string) {
|
||||
if (item in this.items) return new CatalogItemAccessor(this.items[item]);
|
||||
throw new Error(`Item ${item} does not exist in socket metadata`);
|
||||
}
|
||||
|
||||
add(item: CatalogItemAccessor) {
|
||||
this.items[item.id] = item.pack();
|
||||
}
|
||||
|
||||
filter(predicate: (item: string) => boolean) {
|
||||
const items: CatalogItemAccessor[] = [];
|
||||
|
||||
for (const item in this.items) {
|
||||
if (predicate(item)) {
|
||||
items.push(new CatalogItemAccessor(this.items[item]));
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
async strings(locale: string) {
|
||||
const clientConfig = await this.get(
|
||||
'ClientConfigsEntity.default_client_config',
|
||||
);
|
||||
const localizationResources = clientConfig.get(
|
||||
LocalizationResourcesComponent,
|
||||
'localizationResourcesComponent',
|
||||
);
|
||||
|
||||
if (!localizationResources.remote_storage) {
|
||||
throw new Error('Localization resources not found');
|
||||
}
|
||||
|
||||
const { relative_path } = localizationResources.remote_storage;
|
||||
|
||||
let remoteStorage: RemoteStorageComponent | undefined = undefined;
|
||||
|
||||
for (const candidateId of localizationResources.remote_storage.production) {
|
||||
const item = await this.get(candidateId);
|
||||
const candidate = item.get(
|
||||
RemoteStorageComponent,
|
||||
'remoteStorageComponent',
|
||||
);
|
||||
|
||||
if (
|
||||
remoteStorage === undefined ||
|
||||
candidate.download_speed_weight > remoteStorage.download_speed_weight
|
||||
) {
|
||||
remoteStorage = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (remoteStorage === undefined) {
|
||||
throw new Error('No suitable production remote storage found');
|
||||
}
|
||||
|
||||
const config = await fetch(
|
||||
`${remoteStorage.url}/${relative_path}/config.yaml`,
|
||||
)
|
||||
.then((response) => response.text())
|
||||
.then((text) => load(text) as LocalizationConfig);
|
||||
const strings: Record<string, string> = {};
|
||||
|
||||
for (const namespace of config.namespaces) {
|
||||
Object.assign(
|
||||
strings,
|
||||
await fetch(
|
||||
`${remoteStorage.url}/${relative_path}/${namespace}/${locale}.yaml`,
|
||||
)
|
||||
.then((response) => response.text())
|
||||
.then((text) => {
|
||||
const strings: Record<string, string> = {};
|
||||
const parsed = load(text) as Record<string, string>;
|
||||
|
||||
for (const key in parsed) {
|
||||
strings[key] = parsed[key].replaceAll('\\"', '"');
|
||||
}
|
||||
|
||||
return strings;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return strings;
|
||||
}
|
||||
|
||||
async stringsPartial(locale: string, patterns: string[]) {
|
||||
const strings = await this.strings(locale);
|
||||
const filtered: Record<string, string> = {};
|
||||
|
||||
for (const key in strings) {
|
||||
if (patterns.some((pattern) => minimatch(key, pattern))) {
|
||||
filtered[key] = strings[key];
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
toArray() {
|
||||
return Object.values(this.items);
|
||||
}
|
||||
|
||||
toSortedArray() {
|
||||
return this.toArray().sort((a, b) =>
|
||||
a.catalog_id.localeCompare(b.catalog_id),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { BlitzKitAvatars } from '@protos/blitzkit';
|
||||
import { MetadataAccessor } from '../../blitz/metadata';
|
||||
|
||||
export abstract class BlitzkitAPI {
|
||||
abstract metadata: MetadataAccessor;
|
||||
|
||||
abstract avatars(): Promise<BlitzKitAvatars>;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { BlitzKitAvatars } from '@protos/blitzkit';
|
||||
import { MetadataAccessor } from '../../blitz/metadata';
|
||||
import { BlitzkitAPI } from './abstract';
|
||||
|
||||
export class BlitzkitClientAPI extends BlitzkitAPI {
|
||||
get metadata(): MetadataAccessor {
|
||||
throw new Error('Cannot access metadata from client');
|
||||
}
|
||||
|
||||
async avatars() {
|
||||
const response = await fetch('/api/internal/avatars.pb');
|
||||
const buffer = await response.arrayBuffer();
|
||||
return BlitzKitAvatars.decode(new Uint8Array(buffer));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './abstract';
|
||||
export * from './client';
|
||||
export * from './server';
|
||||
@@ -0,0 +1,37 @@
|
||||
import { ProfileAvatarComponent } from '@protos/blitz_static_profile_avatar_component';
|
||||
import { SellableComponent } from '@protos/blitz_static_sellable_component';
|
||||
import { StuffUIComponent } from '@protos/blitz_static_stuff_ui_component';
|
||||
import { BlitzKitAvatars } from '@protos/blitzkit';
|
||||
import { BlitzkitAPI } from '.';
|
||||
import { MetadataAccessor } from '../../blitz/metadata';
|
||||
|
||||
export class BlitzkitServerAPI extends BlitzkitAPI {
|
||||
constructor(public metadata: MetadataAccessor) {
|
||||
super();
|
||||
}
|
||||
|
||||
async avatars() {
|
||||
const avatars: BlitzKitAvatars = { avatars: [] };
|
||||
|
||||
for (const item of this.metadata.filter((item) =>
|
||||
item.startsWith('ProfileAvatarEntity.'),
|
||||
)) {
|
||||
const stuff = item.get(StuffUIComponent, 'UIComponent');
|
||||
const avatar = item.get(ProfileAvatarComponent, 'profileAvatarComponent');
|
||||
const sellable = item.getOptional(SellableComponent, 'sellableComponent');
|
||||
|
||||
avatars.avatars.push({
|
||||
id: item.undiscriminatedId(),
|
||||
name: stuff.display_name,
|
||||
obtaining: stuff.obtaining_methods,
|
||||
description: stuff.description,
|
||||
grade: stuff.grade,
|
||||
category: avatar.category,
|
||||
hidden_if_not_obtained: avatar.hidden_if_not_obtained,
|
||||
sale: sellable?.reward,
|
||||
});
|
||||
}
|
||||
|
||||
return avatars;
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ export * from './uniqueGuns';
|
||||
export * from './unwrapBlitzkitResponse';
|
||||
export * from './votes';
|
||||
// export * from './withCORSProxy';
|
||||
export * from './api';
|
||||
export * from './createDefaultProvisions';
|
||||
export * from './gallery';
|
||||
export * from './metaSortTank';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import locales from '@blitzkit/i18n/locales.json' with { type: 'json' };
|
||||
import { deburr } from 'lodash-es';
|
||||
import { SUPPORTED_LOCALES } from '../../../i18n/src/strings';
|
||||
import { I18nString } from '../protos';
|
||||
import { fetchCamouflageDefinitions } from './camouflageDefinitions';
|
||||
import { fetchTankDefinitions } from './tankDefinitions';
|
||||
@@ -26,8 +26,8 @@ export async function fetchTankNames() {
|
||||
searchableNameDeburr,
|
||||
camouflages: tank.camouflages
|
||||
?.map((id) =>
|
||||
SUPPORTED_LOCALES.map(
|
||||
(locale) =>
|
||||
locales.supported.map(
|
||||
({ locale }) =>
|
||||
camouflageDefinitions.camouflages[id]?.name.locales[locale],
|
||||
),
|
||||
)
|
||||
@@ -42,9 +42,11 @@ export async function fetchTankNames() {
|
||||
}
|
||||
|
||||
export const SEARCH_KEYS = [
|
||||
...SUPPORTED_LOCALES.map((locale) => [
|
||||
...locales.supported
|
||||
.map(({ locale }) => [
|
||||
`searchableName.locales.${locale}`,
|
||||
`searchableNameDeburr.locales.${locale}`,
|
||||
]).flat(),
|
||||
])
|
||||
.flat(),
|
||||
'camouflages',
|
||||
];
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src/**/*", "../i18n/src/strings.ts"]
|
||||
"include": ["src/**/*"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../../"
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,5 @@
|
||||
"name": "@blitzkit/i18n",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"dependencies": {
|
||||
"ts-deepmerge": "^7.0.2"
|
||||
},
|
||||
"devDependencies": {}
|
||||
"main": "src/index.ts"
|
||||
}
|
||||
@@ -1,89 +1,8 @@
|
||||
import { merge } from 'ts-deepmerge';
|
||||
import en from '../strings/en.json';
|
||||
import es from '../strings/es.json';
|
||||
import fr from '../strings/fr.json';
|
||||
import ja from '../strings/ja.json';
|
||||
import pl from '../strings/pl.json';
|
||||
import pt from '../strings/pt.json';
|
||||
import ru from '../strings/ru.json';
|
||||
import uk from '../strings/uk.json';
|
||||
import zh from '../strings/zh.json';
|
||||
import type en from '../strings/en.json';
|
||||
|
||||
export type BlitzKitStrings = typeof en;
|
||||
|
||||
const stringsRaw = { en, es, fr, ja, pl, pt, ru, uk, zh };
|
||||
|
||||
export const SUPPORTED_LOCALES = Object.keys(stringsRaw) as SupportedLocale[];
|
||||
|
||||
export const DEFAULT_LOCALE = SUPPORTED_LOCALES[0];
|
||||
|
||||
function redact(string: string) {
|
||||
const matches = [...string.matchAll(/%s\d+/g)];
|
||||
const characters = string
|
||||
.split('')
|
||||
.map((char) => (char === ' ' ? ' ' : '█')) as string[];
|
||||
|
||||
matches.forEach((match) => {
|
||||
const start = match.index;
|
||||
const matchText = match[0];
|
||||
for (let i = 0; i < matchText.length; i++) {
|
||||
characters[start + i] = matchText[i];
|
||||
}
|
||||
});
|
||||
|
||||
return characters.join('');
|
||||
}
|
||||
|
||||
function nuke(object: TranslationTree) {
|
||||
for (const key in object) {
|
||||
const typedKey = key as keyof typeof object;
|
||||
|
||||
if (typeof object[typedKey] === 'string') {
|
||||
const value = object[typedKey];
|
||||
object[typedKey] = redact(value as string);
|
||||
} else nuke(object[typedKey] as TranslationTree);
|
||||
}
|
||||
}
|
||||
|
||||
const stringsPartial: Partial<Record<SupportedLocale, TranslationTree>> = {};
|
||||
|
||||
for (const locale in stringsRaw) {
|
||||
if (locale === DEFAULT_LOCALE) {
|
||||
stringsPartial[DEFAULT_LOCALE] = stringsRaw[DEFAULT_LOCALE];
|
||||
continue;
|
||||
}
|
||||
|
||||
const localeStrings = stringsRaw[locale as SupportedLocale];
|
||||
stringsPartial[locale as SupportedLocale] = merge(
|
||||
stringsRaw[DEFAULT_LOCALE],
|
||||
localeStrings,
|
||||
);
|
||||
|
||||
if (import.meta.env.PUBLIC_DEBUG_MISSING_I18N === 'true') {
|
||||
nuke(stringsPartial[locale as SupportedLocale]!);
|
||||
}
|
||||
}
|
||||
|
||||
export const STRINGS = stringsPartial as Record<
|
||||
SupportedLocale,
|
||||
BlitzKitStrings
|
||||
>;
|
||||
|
||||
export type SupportedLocale = keyof typeof stringsRaw;
|
||||
|
||||
export const SUPPORTED_LOCALE_BLITZ_MAP: Record<SupportedLocale, string> = {
|
||||
en: 'en',
|
||||
es: 'es',
|
||||
fr: 'fr',
|
||||
ja: 'ja',
|
||||
pt: 'pt',
|
||||
ru: 'ru',
|
||||
uk: 'uk',
|
||||
zh: 'zh-Hans',
|
||||
pl: 'pl',
|
||||
};
|
||||
|
||||
export const SUPPORTED_LOCALE_FLAGS: Record<SupportedLocale, string> = {
|
||||
export const SUPPORTED_LOCALE_FLAGS: Record<string, string> = {
|
||||
en: '🇺🇸',
|
||||
es: '🇪🇸',
|
||||
fr: '🇫🇷',
|
||||
@@ -95,6 +14,14 @@ export const SUPPORTED_LOCALE_FLAGS: Record<SupportedLocale, string> = {
|
||||
pl: '🇵🇱',
|
||||
};
|
||||
|
||||
export type TranslationTree = { [key: string]: TranslationNode };
|
||||
|
||||
export type TranslationNode = string | TranslationTree;
|
||||
export const LOCALE_NAMES: Record<string, string> = {
|
||||
en: 'English',
|
||||
es: 'Español',
|
||||
fr: 'Français',
|
||||
ja: '日本語',
|
||||
pt: 'Português',
|
||||
ru: 'Русский',
|
||||
uk: 'Українська',
|
||||
zh: '中文',
|
||||
pl: 'Polski',
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { I18nString } from '@blitzkit/core';
|
||||
import { DEFAULT_LOCALE } from './strings';
|
||||
import locales from '../locales.json' with { type: 'json' };
|
||||
|
||||
export function unwrapper(locale: string) {
|
||||
return function (i18nString: I18nString) {
|
||||
@@ -7,6 +7,6 @@ export function unwrapper(locale: string) {
|
||||
return i18nString.locales[locale];
|
||||
}
|
||||
|
||||
return i18nString.locales[DEFAULT_LOCALE];
|
||||
return i18nString.locales[locales.default];
|
||||
};
|
||||
}
|
||||
@@ -6,6 +6,14 @@
|
||||
},
|
||||
|
||||
"common": {
|
||||
"grades": {
|
||||
"0": "Unspecified grade",
|
||||
"1": "Common",
|
||||
"2": "Rare",
|
||||
"3": "Epic",
|
||||
"4": "Legendary"
|
||||
},
|
||||
|
||||
"units": {
|
||||
"m": "%s1m",
|
||||
"mm": "%s1mm",
|
||||
@@ -13,7 +21,8 @@
|
||||
"hp": "%s1hp",
|
||||
"x": "x%s1",
|
||||
"percentage": "%s1%",
|
||||
"deg_s": "%s1°/s"
|
||||
"deg_s": "%s1°/s",
|
||||
"times": "x%s1"
|
||||
},
|
||||
|
||||
"gun_types": {
|
||||
@@ -861,6 +870,13 @@
|
||||
},
|
||||
|
||||
"tools": {
|
||||
"internal": {
|
||||
"name": "Internal",
|
||||
"button": "Internal",
|
||||
"description": "BlitzKit internal tools",
|
||||
"seo_description": "BlitzKit internal tools"
|
||||
},
|
||||
|
||||
"guess": {
|
||||
"name": "Guess the tank",
|
||||
"button": "Guess away",
|
||||
@@ -1257,17 +1273,33 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"gallery": {
|
||||
"name": "Gallery",
|
||||
|
||||
"avatars": {
|
||||
"name": "Avatars",
|
||||
"button": "Browse all",
|
||||
"description": "Blitz player avatars",
|
||||
"seo_description": "All World of Tanks Blitz player avatars",
|
||||
"last_updated": "Last updated on %s1",
|
||||
"seo_description": "All World of Tanks Blitz player profile avatars",
|
||||
"description": "Player profile avatars",
|
||||
|
||||
"no_description": "This avatar has no description.",
|
||||
"download": "Download",
|
||||
"enlarge": "Enlarge",
|
||||
"hidden": "Hidden",
|
||||
"unnamed_avatars": "Unnamed avatars",
|
||||
"search": {
|
||||
"hint": "Search avatars...",
|
||||
"results": "%s1 avatars"
|
||||
},
|
||||
|
||||
"categories": {
|
||||
"0": "Unspecified",
|
||||
"1": "Premium",
|
||||
"2": "Special",
|
||||
"3": "Accumulative",
|
||||
"4": "Other",
|
||||
"5": "Temporary"
|
||||
}
|
||||
},
|
||||
|
||||
"session": {
|
||||
"name": "Session",
|
||||
"button": "Start tracking",
|
||||
|
||||
@@ -12,22 +12,5 @@
|
||||
"build:bot": "bun src/buildBot.ts",
|
||||
"build:ratingleaderboard": "bun src/buildRatingLeaderboard.ts",
|
||||
"prunestrings": "bun src/pruneStrings.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzkit/core": "workspace:*",
|
||||
"@blitzkit/i18n": "workspace:*",
|
||||
"@gltf-transform/core": "^4.0.10",
|
||||
"@gltf-transform/extensions": "^4.0.10",
|
||||
"@gltf-transform/functions": "^4.0.10",
|
||||
"csv-parse": "^5.5.6",
|
||||
"decompress": "^4.2.1",
|
||||
"esbuild": "^0.19.12",
|
||||
"fast-xml-parser": "^4.4.1",
|
||||
"progress": "^2.0.3",
|
||||
"puppeteer": "^23.10.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/decompress": "^4.2.7",
|
||||
"@types/progress": "^2.0.7"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import { exec as execSync } from 'child_process';
|
||||
import { existsSync } from 'fs';
|
||||
import { readdir, rm, writeFile } from 'fs/promises';
|
||||
import { chunk } from 'lodash-es';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const ROOT = 'packages/core/src/protos';
|
||||
|
||||
const exec = promisify(execSync);
|
||||
|
||||
let executableFileExtension: string;
|
||||
@@ -15,33 +14,44 @@ if (existsSync('node_modules/.bin/protoc-gen-ts_proto.exe')) {
|
||||
executableFileExtension = '';
|
||||
}
|
||||
|
||||
const filesRaw = await readdir(`${ROOT}`);
|
||||
await Promise.all(
|
||||
filesRaw.map(async (file) => {
|
||||
if (!file.endsWith('.proto')) {
|
||||
await rm(`${ROOT}/${file}`);
|
||||
}
|
||||
}),
|
||||
);
|
||||
// await compile('packages/core/src/protos');
|
||||
await compile('submodules/blitzkit-closed/src/unreal/protos');
|
||||
|
||||
const files = filesRaw.filter((file) => file.endsWith('.proto'));
|
||||
const args = [
|
||||
async function compile(root: string) {
|
||||
const filesRaw = await readdir(root);
|
||||
|
||||
for (const file of filesRaw) {
|
||||
if (!file.endsWith('.ts')) continue;
|
||||
await rm(`${root}/${file}`);
|
||||
}
|
||||
|
||||
const allFiles = filesRaw.filter(
|
||||
(file) => file.endsWith('.proto') && !file.startsWith('_'),
|
||||
);
|
||||
|
||||
for (const files of chunk(allFiles, 32)) {
|
||||
const args = [
|
||||
`-I=${root}`,
|
||||
`--plugin=./node_modules/.bin/protoc-gen-ts_proto${executableFileExtension}`,
|
||||
'--ts_proto_opt=esModuleInterop=true',
|
||||
'--ts_proto_opt=oneof=unions-value',
|
||||
'--ts_proto_opt=removeEnumPrefix=true',
|
||||
// '--ts_proto_opt=removeEnumPrefix=true',
|
||||
'--ts_proto_opt=unrecognizedEnum=false',
|
||||
'--ts_proto_opt=snakeToCamel=false',
|
||||
'--ts_proto_out=.',
|
||||
...files.map((file) => `${ROOT}/${file}`),
|
||||
];
|
||||
`--ts_proto_out=${root}`,
|
||||
...files.map((file) => `${root}/${file}`),
|
||||
];
|
||||
|
||||
await exec(`protoc ${args.join(' ')}`);
|
||||
await writeFile(
|
||||
`${ROOT}/index.ts`,
|
||||
await exec(`protoc ${args.join(' ')}`);
|
||||
}
|
||||
|
||||
await writeFile(
|
||||
`${root}/index.ts`,
|
||||
'// @ts-nocheck\n' +
|
||||
files
|
||||
allFiles
|
||||
.sort()
|
||||
.map((file) => file.replace('.proto', ''))
|
||||
.map((file) => `export * from './${file}.ts';`)
|
||||
.join('\n'),
|
||||
);
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src/**/*"]
|
||||
"include": ["src/**/*"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../../"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# build output
|
||||
dist/
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
@@ -0,0 +1,47 @@
|
||||
# Astro Starter Kit: Minimal
|
||||
|
||||
```sh
|
||||
bun create astro@latest -- --template minimal
|
||||
```
|
||||
|
||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal)
|
||||
[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/minimal)
|
||||
[](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/minimal/devcontainer.json)
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```text
|
||||
/
|
||||
├── public/
|
||||
├── src/
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.json
|
||||
```
|
||||
|
||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
|
||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `bun install` | Installs dependencies |
|
||||
| `bun dev` | Starts local dev server at `localhost:4321` |
|
||||
| `bun build` | Build your production site to `./dist/` |
|
||||
| `bun preview` | Preview your build locally, before deploying |
|
||||
| `bun astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `bun astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||
@@ -0,0 +1,14 @@
|
||||
// @ts-check
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
import react from '@astrojs/react';
|
||||
|
||||
export default defineConfig({
|
||||
integrations: [react(), sitemap()],
|
||||
devToolbar: { enabled: false },
|
||||
outDir: '../../dist/website',
|
||||
|
||||
prefetch: true,
|
||||
site: 'https://blitzkit.app',
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@blitzkit/website-ue",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"lint": "astro check"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/partytown": "^2.1.4",
|
||||
"@astrojs/react": "^4.3.0",
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"@astrolib/seo": "^1.0.0-beta.8",
|
||||
"@blitzkit/closed": "workspace:*",
|
||||
"@blitzkit/core": "workspace:*",
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@radix-ui/themes": "^3.1.4",
|
||||
"@react-three/drei": "^9.115.0",
|
||||
"@react-three/fiber": "^8.17.10",
|
||||
"@types/ffi-napi": "^4.0.10",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/pako": "^2.0.3",
|
||||
"@types/react": "^19.1.5",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"@types/ref-napi": "^3.0.12",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/three": "^0.160.0",
|
||||
"astro": "^5.8.0",
|
||||
"diff": "^8.0.2",
|
||||
"ffi-napi": "^4.0.3",
|
||||
"fuzzysort": "^3.1.0",
|
||||
"immer": "^10.1.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"minimatch": "^10.0.1",
|
||||
"minizlib": "^3.0.2",
|
||||
"pako": "^2.1.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"ref-napi": "^3.0.3",
|
||||
"semver": "^7.7.2",
|
||||
"sharp": "^0.34.2",
|
||||
"three": "^0.169.0",
|
||||
"zustand": "^4.5.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_741_237)">
|
||||
<rect width="60" height="60" fill="#FDD1EA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M54.6274 0L55.4558 0.828427L54.0416 2.24264L51.799 0L54.6274 0ZM5.37258 0L4.54416 0.828427L5.95837 2.24264L8.20101 0L5.37258 0ZM48.9706 0L52.6274 3.65685L51.2132 5.07107L46.1421 0L48.9706 0ZM11.0294 0L7.37258 3.65685L8.7868 5.07107L13.8579 0L11.0294 0ZM43.3137 0L49.799 6.48528L48.3848 7.8995L40.4853 0H43.3137ZM16.6863 0L10.201 6.48528L11.6152 7.8995L19.5147 0L16.6863 0ZM37.6569 0L46.9706 9.31371L45.5564 10.7279L34.8284 0L37.6569 0ZM22.3431 0L13.0294 9.31371L14.4437 10.7279L25.1716 0L22.3431 0ZM32 0L44.1421 12.1421L42.7279 13.5563L30 0.828427L17.2721 13.5563L15.8579 12.1421L28 0H32ZM0.284271 0L28.2843 28L26.8701 29.4142L0 2.54416V0L0.284271 0ZM0 5.37258L25.4558 30.8284L24.0416 32.2426L0 8.20101L0 5.37258ZM0 11.0294L22.6274 33.6569L21.2132 35.0711L0 13.8579L0 11.0294ZM0 16.6863L19.799 36.4853L18.3848 37.8995L0 19.5147L0 16.6863ZM0 22.3431L16.9706 39.3137L15.5563 40.7279L0 25.1716L0 22.3431ZM0 28L14.1421 42.1421L12.7279 43.5564L0 30.8284V28ZM0 33.6569L11.3137 44.9706L9.8995 46.3848L0 36.4853L0 33.6569ZM0 39.3137L8.48528 47.799L7.07107 49.2132L0 42.1421L0 39.3137ZM0 44.9706L5.65685 50.6274L4.24264 52.0416L0 47.799L0 44.9706ZM0 50.6274L2.82843 53.4558L1.41421 54.8701L0 53.4558L0 50.6274ZM54.6274 60L30 35.3726L5.37258 60H8.20101L30 38.201L51.799 60H54.6274ZM48.9706 60L30 41.0294L11.0294 60H13.8579L30 43.8579L46.1421 60H48.9706ZM43.3137 60L30 46.6863L16.6863 60H19.5147L30 49.5147L40.4853 60H43.3137ZM37.6569 60L30 52.3431L22.3431 60H25.1716L30 55.1716L34.8284 60H37.6569ZM32 60L30 58L28 60H32ZM59.7157 0L31.7157 28L33.1299 29.4142L60 2.54416V0L59.7157 0ZM60 5.37258L34.5442 30.8284L35.9584 32.2426L60 8.20101V5.37258ZM60 11.0294L37.3726 33.6569L38.7868 35.0711L60 13.8579V11.0294ZM60 16.6863L40.201 36.4853L41.6152 37.8995L60 19.5147V16.6863ZM60 22.3431L43.0294 39.3137L44.4436 40.7279L60 25.1716V22.3431ZM60 28L45.8579 42.1421L47.2721 43.5564L60 30.8284V28ZM60 33.6569L48.6863 44.9706L50.1005 46.3848L60 36.4853V33.6569ZM60 39.3137L51.5147 47.799L52.9289 49.2132L60 42.1421V39.3137ZM60 44.9706L54.3431 50.6274L55.7574 52.0416L60 47.799V44.9706ZM60 50.6274L57.1716 53.4558L58.5858 54.8701L60 53.4558V50.6274ZM39.8995 16.3848L41.3137 14.9706L30 3.65685L18.6863 14.9706L20.1005 16.3848L30 6.48528L39.8995 16.3848ZM37.0711 19.2132L38.4853 17.799L30 9.31371L21.5147 17.799L22.9289 19.2132L30 12.1421L37.0711 19.2132ZM34.2426 22.0416L35.6569 20.6274L30 14.9706L24.3431 20.6274L25.7574 22.0416L30 17.799L34.2426 22.0416ZM31.4142 24.8701L32.8284 23.4558L30 20.6274L27.1716 23.4558L28.5858 24.8701L30 23.4558L31.4142 24.8701ZM56.8701 59.4142L58.2843 58L30 29.7157L1.71573 58L3.12994 59.4142L30 32.5442L56.8701 59.4142Z" fill="#FFBECE"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_741_237">
|
||||
<rect width="60" height="60" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 676 KiB |
|
After Width: | Height: | Size: 443 KiB |
|
After Width: | Height: | Size: 330 KiB |
|
After Width: | Height: | Size: 715 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 346 KiB |
|
After Width: | Height: | Size: 406 KiB |
|
After Width: | Height: | Size: 360 KiB |
|
After Width: | Height: | Size: 193 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 692 B |
|
After Width: | Height: | Size: 692 B |
@@ -0,0 +1,10 @@
|
||||
import { Grade } from '@protos/blitz_static_standard_grades_enum';
|
||||
import type { BadgeProps } from '@radix-ui/themes';
|
||||
|
||||
export const GRADE_COLORS = {
|
||||
[Grade.GRADE_UNSPECIFIED]: 'gray',
|
||||
[Grade.GRADE_COMMON]: 'gray',
|
||||
[Grade.GRADE_RARE]: 'blue',
|
||||
[Grade.GRADE_EPIC]: 'purple',
|
||||
[Grade.GRADE_LEGENDARY]: 'amber',
|
||||
} satisfies Record<Grade, BadgeProps['color']>;
|
||||
@@ -0,0 +1,57 @@
|
||||
import { type FlexProps, Box, Flex, Skeleton, Text } from '@radix-ui/themes';
|
||||
import { useRef } from 'react';
|
||||
import { useIntersection } from '../../hooks/useIntersection';
|
||||
import { useLocale } from '../../hooks/useLocale';
|
||||
import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps';
|
||||
import { InlineSkeleton } from '../InlineSkeleton';
|
||||
import type { AvatarGroupProps } from './Group';
|
||||
import { Stack } from './Stack';
|
||||
|
||||
const MAX_AVATARS_PREVIEW = 5;
|
||||
|
||||
export function Content({
|
||||
...props
|
||||
}: FlexProps & MaybeSkeletonComponentProps<AvatarGroupProps>) {
|
||||
const { strings } = useLocale();
|
||||
const container = useRef<HTMLDivElement>(null);
|
||||
|
||||
useIntersection(() => props.skeleton && props.onIntersection?.(), container, {
|
||||
disabled: !props.skeleton || props.onIntersection === undefined,
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex
|
||||
ref={container}
|
||||
direction="column"
|
||||
gap="3"
|
||||
style={{ cursor: props.skeleton ? undefined : 'pointer' }}
|
||||
{...props}
|
||||
>
|
||||
<Box width="100%" style={{ aspectRatio: '7 / 8' }} position="relative">
|
||||
{props.skeleton && <Skeleton height="100%" />}
|
||||
|
||||
{!props.skeleton && (
|
||||
<Stack avatars={[...props.avatars].slice(-MAX_AVATARS_PREVIEW)} />
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Text
|
||||
size="2"
|
||||
wrap="nowrap"
|
||||
align="center"
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
fontStyle:
|
||||
!props.skeleton && props.name === undefined ? 'italic' : undefined,
|
||||
}}
|
||||
>
|
||||
{props.skeleton ? (
|
||||
<InlineSkeleton width="100%" />
|
||||
) : (
|
||||
(props.name ?? strings.website.tools.avatars.unnamed_avatars)
|
||||
)}
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { BlitzKitAvatar } from '@protos/blitzkit';
|
||||
import { Dialog } from '@radix-ui/themes';
|
||||
import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps';
|
||||
import { Content } from './Content';
|
||||
import { Popup } from './Popup';
|
||||
|
||||
export interface AvatarGroupProps {
|
||||
name?: string;
|
||||
avatars: { avatar: BlitzKitAvatar }[];
|
||||
}
|
||||
|
||||
export function AvatarGroup(
|
||||
props: MaybeSkeletonComponentProps<AvatarGroupProps>,
|
||||
) {
|
||||
if (props.skeleton) return <Content {...props} />;
|
||||
|
||||
return (
|
||||
<Dialog.Root>
|
||||
<Dialog.Trigger>
|
||||
<Content {...props} />
|
||||
</Dialog.Trigger>
|
||||
|
||||
<Dialog.Content>
|
||||
<Popup {...props} />
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import { Grade } from '@protos/blitz_static_standard_grades_enum';
|
||||
import {
|
||||
CardStackIcon,
|
||||
DownloadIcon,
|
||||
ExternalLinkIcon,
|
||||
EyeNoneIcon,
|
||||
StarFilledIcon,
|
||||
} from '@radix-ui/react-icons';
|
||||
import { Badge, Box, Button, Dialog, Flex, Link, Text } from '@radix-ui/themes';
|
||||
import { Var } from '../../core/radix/var';
|
||||
import { useLocale } from '../../hooks/useLocale';
|
||||
import { RewardBadge } from '../RewardBadge';
|
||||
import { GRADE_COLORS } from './Colors';
|
||||
import type { AvatarGroupProps } from './Group';
|
||||
|
||||
export function Popup({ name, avatars }: AvatarGroupProps) {
|
||||
const { strings, gameStrings } = useLocale();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog.Title>{name}</Dialog.Title>
|
||||
|
||||
<Flex direction="column" gap="5" mt="5">
|
||||
{[...avatars].reverse().map(({ avatar }, index) => (
|
||||
<Flex key={index} gap="5">
|
||||
<Box
|
||||
m="1"
|
||||
mb="4"
|
||||
height="10rem"
|
||||
style={{
|
||||
aspectRatio: '7 / 8',
|
||||
borderRadius: Var('radius-4'),
|
||||
outline: `${Var('space-1')} solid ${Var(`${GRADE_COLORS[avatar.grade]}-9`)}`,
|
||||
boxShadow: `0 0 ${
|
||||
1.5 * (avatar.grade / Grade.GRADE_LEGENDARY)
|
||||
}rem ${Var(`${GRADE_COLORS[avatar.grade]}-9`)}`,
|
||||
backgroundImage: 'url(/assets/sad-ghost.png)',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
backgroundSize: '3rem',
|
||||
}}
|
||||
position="relative"
|
||||
>
|
||||
<Box
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{
|
||||
backgroundImage: `url(/api/avatars/${avatar.id}.webp)`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Badge
|
||||
mt="1"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '100%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}}
|
||||
variant="solid"
|
||||
color={GRADE_COLORS[avatar.grade]}
|
||||
>
|
||||
<StarFilledIcon /> {strings.common.grades[avatar.grade]}
|
||||
</Badge>
|
||||
</Box>
|
||||
|
||||
<Flex direction="column" gap="3" justify="center">
|
||||
<Flex gap="1">
|
||||
<Badge color="gray" variant="surface">
|
||||
<CardStackIcon />
|
||||
{strings.website.tools.avatars.categories[avatar.category]}
|
||||
</Badge>
|
||||
|
||||
{avatar.hidden_if_not_obtained && (
|
||||
<Badge color="gray" variant="surface">
|
||||
<EyeNoneIcon /> {strings.website.tools.avatars.hidden}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{avatar.sale && (
|
||||
<RewardBadge
|
||||
reward={avatar.sale}
|
||||
color="gray"
|
||||
variant="surface"
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Text>
|
||||
{gameStrings[avatar.description] ??
|
||||
(gameStrings[avatar.obtaining]
|
||||
? undefined
|
||||
: strings.website.tools.avatars.no_description)}{' '}
|
||||
{gameStrings[avatar.obtaining]}
|
||||
</Text>
|
||||
|
||||
<Flex gap="2">
|
||||
<Link href={`/api/avatars/${avatar.id}.webp`} download>
|
||||
<Button variant="solid">
|
||||
<DownloadIcon /> {strings.website.tools.avatars.download}
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link href={`/api/avatars/${avatar.id}.webp`} target="_blank">
|
||||
<Button variant="outline">
|
||||
<ExternalLinkIcon /> {strings.website.tools.avatars.enlarge}
|
||||
</Button>
|
||||
</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Badge, Box } from '@radix-ui/themes';
|
||||
import { literals } from 'packages/i18n/src';
|
||||
import { Var } from '../../core/radix/var';
|
||||
import { useLocale } from '../../hooks/useLocale';
|
||||
import type { AvatarGroupProps } from './Group';
|
||||
|
||||
const dx = '4px';
|
||||
|
||||
export function Stack({ avatars }: { avatars: AvatarGroupProps['avatars'] }) {
|
||||
const { strings } = useLocale();
|
||||
|
||||
return (
|
||||
<>
|
||||
{avatars.map(({ avatar }, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
position="absolute"
|
||||
top={`calc(${dx} * ${index})`}
|
||||
left={`calc(${dx} * ${index})`}
|
||||
width={`calc(100% - ${dx} * ${avatars.length - 1})`}
|
||||
height={`calc(100% - ${dx} * ${avatars.length - 1})`}
|
||||
style={{
|
||||
filter: `brightness(${(index + 1) / avatars.length})`,
|
||||
borderRadius: Var('radius-4'),
|
||||
boxShadow: Var('shadow-2'),
|
||||
overflow: 'hidden',
|
||||
backgroundImage: 'url(/assets/sad-ghost.png)',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
backgroundSize: '3rem',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{
|
||||
backgroundImage: `url(/api/avatars/${avatar.id}.webp)`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
/>
|
||||
|
||||
{avatars.length > 1 && index === avatars.length - 1 && (
|
||||
<Box
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{
|
||||
background: `linear-gradient(-45deg, ${Var('black-a9')} 25%, ${Var('black-a1')} 50%)`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
|
||||
{avatars.length > 1 && (
|
||||
<Box position="absolute" bottom="1" right="1">
|
||||
<Badge variant="solid" color="gray" highContrast size="2">
|
||||
{literals(strings.common.units.times, [`${avatars.length}`])}
|
||||
</Badge>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Theme, type ThemeProps } from '@radix-ui/themes';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
interface BlitzKitThemeProps extends ThemeProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function BlitzKitTheme({
|
||||
children,
|
||||
className,
|
||||
style,
|
||||
...props
|
||||
}: BlitzKitThemeProps) {
|
||||
return (
|
||||
<Theme
|
||||
style={{ minHeight: 'unset', display: 'contents', ...style }}
|
||||
className={`blitzkit-theme ${className}`}
|
||||
grayColor="mauve"
|
||||
appearance="dark"
|
||||
panelBackground="solid"
|
||||
radius="full"
|
||||
accentColor="iris"
|
||||
suppressHydrationWarning
|
||||
suppressContentEditableWarning
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Theme>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
.policies-agreement {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--color-overlay);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.policies-agreement .content-wrapper {
|
||||
width: 100%;
|
||||
background: var(--color-panel-solid);
|
||||
}
|
||||
|
||||
.policies-agreement .content {
|
||||
max-width: 60rem;
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import { assertSecret, idToRegion } from '@blitzkit/core';
|
||||
import { Button, Flex, Heading, Text } from '@radix-ui/themes';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
LocaleProvider,
|
||||
useLocale,
|
||||
type LocaleAcceptorProps,
|
||||
} from '../../hooks/useLocale';
|
||||
import { App } from '../../stores/app';
|
||||
import { CURRENT_POLICIES_AGREEMENT_INDEX } from '../../stores/app/constants';
|
||||
|
||||
import { LinkI18n } from '../LinkI18n';
|
||||
import './index.css';
|
||||
|
||||
type Extension =
|
||||
| {
|
||||
status: 'ok';
|
||||
data: {
|
||||
access_token: string;
|
||||
account_id: number;
|
||||
expires_at: number;
|
||||
};
|
||||
}
|
||||
| {
|
||||
status: 'error';
|
||||
};
|
||||
|
||||
export function Checks({ localeData }: LocaleAcceptorProps) {
|
||||
return (
|
||||
<LocaleProvider localeData={localeData}>
|
||||
<App.Provider>
|
||||
<Content />
|
||||
</App.Provider>
|
||||
</LocaleProvider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wargaming: 14 (refresh: 7)
|
||||
*/
|
||||
function Content() {
|
||||
const { locale } = useLocale();
|
||||
const logins = App.use((state) => state.logins);
|
||||
const mutateApp = App.useMutation();
|
||||
const appStore = App.useStore();
|
||||
const policiesAgreementIndex = App.use(
|
||||
(state) => state.policiesAgreementIndex,
|
||||
);
|
||||
const isLegal = window.location.pathname.startsWith('/legal');
|
||||
const [showPoliciesAgreement, setShowPoliciesAgreement] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
function unsubscribe() {
|
||||
window.removeEventListener('scroll', show);
|
||||
window.removeEventListener('pointermove', show);
|
||||
window.removeEventListener('pointerdown', show);
|
||||
}
|
||||
|
||||
function show() {
|
||||
setShowPoliciesAgreement(true);
|
||||
unsubscribe();
|
||||
}
|
||||
|
||||
if (
|
||||
policiesAgreementIndex !== CURRENT_POLICIES_AGREEMENT_INDEX &&
|
||||
!isLegal
|
||||
) {
|
||||
window.addEventListener('scroll', show);
|
||||
window.addEventListener('pointermove', show);
|
||||
window.addEventListener('pointerdown', show);
|
||||
}
|
||||
|
||||
return unsubscribe;
|
||||
}, [policiesAgreementIndex, isLegal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (logins.wargaming) {
|
||||
const expiresIn = logins.wargaming.expires - Date.now();
|
||||
const expiresInDays = expiresIn / 1000 / 60 / 60 / 24;
|
||||
|
||||
if (expiresInDays < 0) {
|
||||
mutateApp((draft) => {
|
||||
draft.logins.wargaming = undefined;
|
||||
});
|
||||
} else if (expiresInDays < 7) {
|
||||
const { wargaming } = appStore.getState().logins;
|
||||
|
||||
if (!wargaming) return;
|
||||
|
||||
fetch(
|
||||
`https://api.worldoftanks.${idToRegion(
|
||||
wargaming.id,
|
||||
)}/wot/auth/prolongate/?application_id=${assertSecret(
|
||||
import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID,
|
||||
)}&access_token=${wargaming.token}`,
|
||||
)
|
||||
.then((response) => response.json() as Promise<Extension>)
|
||||
.then((json) => {
|
||||
mutateApp((draft) => {
|
||||
if (json.status === 'error') {
|
||||
draft.logins.wargaming = undefined;
|
||||
} else {
|
||||
draft.logins.wargaming = {
|
||||
id: wargaming.id,
|
||||
token: json.data.access_token,
|
||||
expires: json.data.expires_at * 1000,
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!showPoliciesAgreement) return null;
|
||||
|
||||
return (
|
||||
<Flex className="policies-agreement" align="end">
|
||||
<Flex p="8" className="content-wrapper" justify="center">
|
||||
<Flex align="start" gap="4" className="content" direction="column">
|
||||
<Flex direction="column" gap="2">
|
||||
<Heading>
|
||||
{policiesAgreementIndex === -1
|
||||
? "BlitzKit's policies"
|
||||
: "BlitzKit's policies have changed"}
|
||||
</Heading>
|
||||
|
||||
<Text>
|
||||
This website utilizes cookies to perform analytics and personalize
|
||||
your experience. You can learn more through{' '}
|
||||
<LinkI18n locale={locale} href="/docs/legal/privacy-policy">
|
||||
our privacy policy
|
||||
</LinkI18n>
|
||||
. By using BlitzKit, you also agree to our{' '}
|
||||
<LinkI18n locale={locale} href="/docs/legal/terms-of-service">
|
||||
terms of service
|
||||
</LinkI18n>
|
||||
.
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Button
|
||||
mt="2"
|
||||
onClick={() => {
|
||||
appStore.setState({
|
||||
policiesAgreementIndex: CURRENT_POLICIES_AGREEMENT_INDEX,
|
||||
});
|
||||
setShowPoliciesAgreement(false);
|
||||
}}
|
||||
>
|
||||
I agree
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Permission denied
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
import { Box, Code, Flex, Heading, Link, Text } from '@radix-ui/themes';
|
||||
import packageJson from '../../../../package.json';
|
||||
import { getStrings } from '../core/i18n/getStrings';
|
||||
import { LinkI18n } from './LinkI18n';
|
||||
import { LocaleSwitcherThemeWrapper } from './LocaleSwitcher';
|
||||
|
||||
interface Props {
|
||||
locale: string;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const localeData = { locale };
|
||||
const strings = await getStrings(locale);
|
||||
---
|
||||
|
||||
<style>
|
||||
.footer {
|
||||
background-color: var(--color-panel);
|
||||
}
|
||||
</style>
|
||||
|
||||
<Flex className="footer" gap="6" p="6" direction="column" align="center">
|
||||
<Flex
|
||||
align="center"
|
||||
justify="between"
|
||||
maxWidth="40rem"
|
||||
width="100%"
|
||||
flexGrow="1"
|
||||
gap="5"
|
||||
position="relative"
|
||||
direction={{ initial: 'column', sm: 'row' }}
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
align={{
|
||||
initial: 'center',
|
||||
sm: 'start',
|
||||
}}
|
||||
>
|
||||
<Flex align="center" gap="2">
|
||||
<Heading> BlitzKit </Heading>
|
||||
<Code size="1" color="gray" variant="ghost">{packageJson.version}</Code>
|
||||
</Flex>
|
||||
|
||||
<Text color="gray">{strings.website.footer.motto}</Text>
|
||||
<Text size="1" color="gray">
|
||||
{strings.website.footer.copyright}
|
||||
</Text>
|
||||
|
||||
<Box mt="2">
|
||||
<LocaleSwitcherThemeWrapper {localeData} client:load />
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
gap="6"
|
||||
width={{
|
||||
initial: '100%',
|
||||
sm: 'auto',
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
gap="1"
|
||||
align={{
|
||||
initial: 'end',
|
||||
sm: 'start',
|
||||
}}
|
||||
flexGrow={{
|
||||
initial: '1',
|
||||
sm: '0',
|
||||
}}
|
||||
flexBasis={{
|
||||
initial: '0',
|
||||
sm: 'auto',
|
||||
}}
|
||||
>
|
||||
<Heading size="3">{strings.website.footer.about.title}</Heading>
|
||||
<Flex
|
||||
direction="column"
|
||||
align={{
|
||||
initial: 'end',
|
||||
sm: 'start',
|
||||
}}
|
||||
>
|
||||
<LinkI18n {locale} href="/docs/about/credits" color="gray">
|
||||
{strings.website.footer.about.credits}
|
||||
</LinkI18n>
|
||||
<Link
|
||||
href="https://discord.gg/nDt7AjGJQH"
|
||||
target="_blank"
|
||||
color="gray"
|
||||
>
|
||||
{strings.website.footer.about.discord}
|
||||
</Link>
|
||||
<Link
|
||||
href="https://github.com/tresabhi/blitzkit"
|
||||
target="_blank"
|
||||
color="gray"
|
||||
>
|
||||
{strings.website.footer.about.github}
|
||||
</Link>
|
||||
<LinkI18n {locale} href="/docs/patches" color="gray">
|
||||
{strings.website.footer.about.patches}
|
||||
</LinkI18n>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
direction="column"
|
||||
gap="1"
|
||||
flexGrow={{
|
||||
initial: '1',
|
||||
sm: '0',
|
||||
}}
|
||||
flexBasis={{
|
||||
initial: '0',
|
||||
sm: 'auto',
|
||||
}}
|
||||
>
|
||||
<Heading size="3">{strings.website.footer.legal.title}</Heading>
|
||||
<Flex direction="column">
|
||||
<LinkI18n {locale} href="/docs/legal/privacy-policy" color="gray">
|
||||
{strings.website.footer.legal.privacy}
|
||||
</LinkI18n>
|
||||
<LinkI18n {locale} href="/docs/legal/terms-of-service" color="gray">
|
||||
{strings.website.footer.legal.terms}
|
||||
</LinkI18n>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
@@ -0,0 +1,156 @@
|
||||
---
|
||||
import { Code, Flex, Heading, Text } from '@radix-ui/themes';
|
||||
import { resolveBranchName } from '../core/blitzkit/branches';
|
||||
import { getStrings } from '../core/i18n/getStrings';
|
||||
import { literals } from '@blitzkit/i18n/src/literals';
|
||||
|
||||
interface Props {
|
||||
locale: string;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const strings = await getStrings(locale);
|
||||
const branchName = resolveBranchName();
|
||||
const isBranchNamed = branchName !== undefined;
|
||||
---
|
||||
|
||||
<style>
|
||||
#home-hero {
|
||||
--p-1: 0%;
|
||||
--p-2: 0%;
|
||||
--p-3: 0%;
|
||||
--p-4: 0%;
|
||||
--p-7: 0%;
|
||||
|
||||
--g-1-dark: var(--blue-3);
|
||||
--g-1-light: var(--blue-7);
|
||||
--g-2-dark: var(--violet-3);
|
||||
--g-2-light: var(--violet-7);
|
||||
--g-3-dark: var(--purple-3);
|
||||
--g-3-light: var(--purple-7);
|
||||
--g-4-dark: var(--crimson-3);
|
||||
--g-4-light: var(--crimson-7);
|
||||
--g-7-dark: var(--tomato-3);
|
||||
--g-7-light: var(--tomato-7);
|
||||
|
||||
--g-1: color-mix(in srgb, var(--g-1-dark), var(--g-1-light) var(--p-1));
|
||||
--g-2: color-mix(in srgb, var(--g-2-dark), var(--g-2-light) var(--p-2));
|
||||
--g-3: color-mix(in srgb, var(--g-3-dark), var(--g-3-light) var(--p-3));
|
||||
--g-4: color-mix(in srgb, var(--g-4-dark), var(--g-4-light) var(--p-4));
|
||||
--g-7: color-mix(in srgb, var(--g-7-dark), var(--g-7-light) var(--p-7));
|
||||
|
||||
background: linear-gradient(
|
||||
45deg,
|
||||
var(--g-1),
|
||||
var(--g-2),
|
||||
var(--g-3),
|
||||
var(--g-4),
|
||||
var(--g-7)
|
||||
);
|
||||
}
|
||||
|
||||
#home-hero[data-variant='pride'] {
|
||||
--g-1-dark: var(--crimson-3);
|
||||
--g-1-light: var(--crimson-7);
|
||||
--g-2-dark: var(--orange-3);
|
||||
--g-2-light: var(--orange-7);
|
||||
--g-3-dark: var(--yellow-3);
|
||||
--g-3-light: var(--yellow-7);
|
||||
--g-4-dark: var(--blue-3);
|
||||
--g-4-light: var(--blue-7);
|
||||
--g-7-dark: var(--purple-3);
|
||||
--g-7-light: var(--purple-7);
|
||||
}
|
||||
|
||||
#home-hero[data-variant='spooky'] {
|
||||
--g-1-dark: var(--orange-3);
|
||||
--g-1-light: var(--orange-7);
|
||||
--g-2-dark: var(--tomato-3);
|
||||
--g-2-light: var(--tomato-7);
|
||||
--g-3-dark: var(--orange-3);
|
||||
--g-3-light: var(--orange-7);
|
||||
--g-4-dark: var(--tomato-3);
|
||||
--g-4-light: var(--tomato-7);
|
||||
--g-7-dark: var(--orange-3);
|
||||
--g-7-light: var(--orange-7);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { clamp, times } from 'lodash-es';
|
||||
|
||||
const n = 5;
|
||||
const homeHero = document.getElementById('home-hero');
|
||||
|
||||
if (homeHero) {
|
||||
switch (new Date().getMonth()) {
|
||||
case 5:
|
||||
homeHero.dataset.variant = 'pride';
|
||||
break;
|
||||
|
||||
case 9:
|
||||
homeHero.dataset.variant = 'spooky';
|
||||
break;
|
||||
}
|
||||
|
||||
window.addEventListener('pointermove', (event) => {
|
||||
const x = event.clientX / window.innerWidth;
|
||||
const c = clamp(
|
||||
(1 -
|
||||
(event.clientY + homeHero.getBoundingClientRect().top) /
|
||||
window.innerHeight) **
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
);
|
||||
|
||||
// https://www.desmos.com/calculator/ndz2poxk32
|
||||
times(n, (i) => {
|
||||
const p = c * clamp(-n * Math.abs(x - i / (n - 1)) + 1, 0, 3);
|
||||
homeHero.style.setProperty(`--p-${i + 1}`, `${p * 100}%`);
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<Flex
|
||||
id="home-hero"
|
||||
align="center"
|
||||
justify="center"
|
||||
position="relative"
|
||||
px="4"
|
||||
py="9"
|
||||
overflow="hidden"
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
gap="6"
|
||||
align="center"
|
||||
justify="center"
|
||||
position="relative"
|
||||
width="100%"
|
||||
>
|
||||
<Flex direction="column" align="center" gap="1">
|
||||
<Heading size={{ initial: '8', xs: '9' }} weight="bold" align="center">
|
||||
<Flex align="center" gap={{ initial: '2', xs: '4' }}>
|
||||
BlitzKit
|
||||
{
|
||||
isBranchNamed && (
|
||||
<Code
|
||||
size={{ initial: '7', xs: '8' }}
|
||||
color="gray"
|
||||
variant="outline"
|
||||
>
|
||||
{branchName}
|
||||
</Code>
|
||||
)
|
||||
}
|
||||
</Flex>
|
||||
</Heading>
|
||||
|
||||
<Text color="gray" align="center" size="4"
|
||||
>{literals(strings.website.home.hero.credit, ['TrèsAbhi'])}</Text
|
||||
>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Skeleton, type SkeletonProps } from '@radix-ui/themes';
|
||||
|
||||
export function InlineSkeleton({ className, ...props }: SkeletonProps) {
|
||||
return (
|
||||
<Skeleton style={{ display: 'inline-block' }} height="1em" {...props} />
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Flex, Text } from '@radix-ui/themes';
|
||||
import { ffi } from '../../core/blitzkit/ffi';
|
||||
|
||||
export function FFIEcho() {
|
||||
return (
|
||||
<Flex direction="column" align="start" gap="2">
|
||||
<Text>{ffi.debug_echo(new Date().toLocaleString())}</Text>
|
||||
<img src="https://i.imgflip.com/9wrr7u.gif" />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Code, Text } from '@radix-ui/themes';
|
||||
import { ffi } from '../../core/blitzkit/ffi';
|
||||
|
||||
export function FFIFileCount() {
|
||||
return (
|
||||
<Text>
|
||||
<Code>{ffi.debug_get_file_count().toLocaleString()}</Code> files loaded
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
import locales from '@blitzkit/i18n/locales.json';
|
||||
import { DiscordLogoIcon, PaperPlaneIcon } from '@radix-ui/react-icons';
|
||||
import { Button, Flex, Heading, Link, Text } from '@radix-ui/themes';
|
||||
import { PageWrapper } from '../components/PageWrapper';
|
||||
import { getStrings } from '../core/i18n/getStrings';
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
|
||||
export interface Props {
|
||||
path: string;
|
||||
locale: string;
|
||||
}
|
||||
|
||||
const { path, locale } = Astro.props;
|
||||
|
||||
const strings = await getStrings(locale);
|
||||
const resolvedPath = `/${locale === locales.default ? '' : `${locale}/`}${path}`;
|
||||
---
|
||||
|
||||
<BaseLayout {locale}>
|
||||
<link
|
||||
slot="head"
|
||||
rel="canonical"
|
||||
href={`${Astro.url.origin}${resolvedPath}`}
|
||||
/>
|
||||
<meta slot="head" http-equiv="refresh" content={`0; url=${resolvedPath}`} />
|
||||
|
||||
<PageWrapper align="center" justify="center" gap="0">
|
||||
<Heading>{strings.website.redirect.title}</Heading>
|
||||
<Text color="gray">{strings.website.redirect.body}</Text>
|
||||
|
||||
<Flex mt="4" gap="2">
|
||||
<Link href={resolvedPath}>
|
||||
<Button>
|
||||
<PaperPlaneIcon /> Redirect
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link href="https://discord.gg/nDt7AjGJQH">
|
||||
<Button variant="outline">
|
||||
<DiscordLogoIcon /> Discord
|
||||
</Button>
|
||||
</Link>
|
||||
</Flex>
|
||||
</PageWrapper>
|
||||
</BaseLayout>
|
||||
@@ -0,0 +1,14 @@
|
||||
import locales from '@blitzkit/i18n/locales.json' with { type: 'json' };
|
||||
import { Link, type LinkProps } from '@radix-ui/themes';
|
||||
|
||||
export function LinkI18n({
|
||||
locale,
|
||||
href,
|
||||
...props
|
||||
}: LinkProps & { locale: string }) {
|
||||
const prefix = locale === locales.default ? '' : `/${locale}`;
|
||||
|
||||
return (
|
||||
<Link href={href?.startsWith('/') ? `${prefix}${href}` : href} {...props} />
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import locales from '@blitzkit/i18n/locales.json' with { type: 'json' };
|
||||
import { Select } from '@radix-ui/themes';
|
||||
import { LOCALE_NAMES, SUPPORTED_LOCALE_FLAGS } from 'packages/i18n/src';
|
||||
import type { LocaleAcceptorProps } from '../hooks/useLocale';
|
||||
import { BlitzKitTheme } from './BlitzKitTheme';
|
||||
|
||||
export function LocaleSwitcherThemeWrapper({
|
||||
localeData,
|
||||
}: LocaleAcceptorProps) {
|
||||
return (
|
||||
<BlitzKitTheme style={{ background: 'transparent' }}>
|
||||
<LocaleSwitcher localeData={localeData} />
|
||||
</BlitzKitTheme>
|
||||
);
|
||||
}
|
||||
|
||||
export function LocaleSwitcher({ localeData }: LocaleAcceptorProps) {
|
||||
return (
|
||||
<Select.Root
|
||||
defaultValue={localeData.locale}
|
||||
onValueChange={(locale) => {
|
||||
localStorage.setItem('preferred-locale', locale);
|
||||
|
||||
let rawPath = window.location.pathname;
|
||||
|
||||
for (const supportedLocale of locales.supported) {
|
||||
if (
|
||||
window.location.pathname.startsWith(`/${supportedLocale.locale}`)
|
||||
) {
|
||||
rawPath = rawPath.replace(`/${supportedLocale.locale}`, '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
window.location.pathname = `${
|
||||
locale === locales.default ? '' : `/${locale}`
|
||||
}${rawPath}`;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger variant="classic" />
|
||||
|
||||
<Select.Content>
|
||||
{locales.supported.map(({ locale }) => (
|
||||
<Select.Item key={locale} value={locale}>
|
||||
{SUPPORTED_LOCALE_FLAGS[locale]} {LOCALE_NAMES[locale]}
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
---
|
||||
import {
|
||||
Cross1Icon,
|
||||
DiscordLogoIcon,
|
||||
GearIcon,
|
||||
HamburgerMenuIcon,
|
||||
} from '@radix-ui/react-icons';
|
||||
import { Box, Code, Flex, IconButton, Link } from '@radix-ui/themes';
|
||||
import { tools } from '../constants/tools';
|
||||
import { resolveBranchName } from '../core/blitzkit/branches';
|
||||
import { getStrings } from '../core/i18n/getStrings';
|
||||
import { Var } from '../core/radix/var';
|
||||
import { LinkI18n } from './LinkI18n';
|
||||
|
||||
export interface Props {
|
||||
locale: string;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const toolsFiltered = tools.filter((tool) => tool.enabled);
|
||||
const branchName = resolveBranchName();
|
||||
const isBranchNamed = branchName !== undefined;
|
||||
const strings = await getStrings(locale);
|
||||
---
|
||||
|
||||
<style>
|
||||
.navbar,
|
||||
.padder {
|
||||
transition-duration: 1s;
|
||||
}
|
||||
|
||||
body.no-navbar .navbar {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
body.no-navbar .padder {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.navbar[data-expanded='false'] *[data-expanded] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar[data-expanded='true'] {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.navbar[data-expanded='false'] .content {
|
||||
background-color: var(--black-a8);
|
||||
backdrop-filter: blur(4rem);
|
||||
box-shadow: var(--shadow-3);
|
||||
}
|
||||
|
||||
.navbar[data-expanded='true'] .content {
|
||||
background-color: var(--gray-1);
|
||||
}
|
||||
|
||||
.navbar[data-expanded='true'] *[data-closed] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const navbar = document.querySelector<HTMLDivElement>('.navbar');
|
||||
const hamburger = document.querySelector<HTMLButtonElement>('.hamburger');
|
||||
|
||||
navbar?.addEventListener('click', () => {
|
||||
navbar.dataset.expanded = 'false';
|
||||
});
|
||||
hamburger?.addEventListener('click', (event) => {
|
||||
if (!navbar) return;
|
||||
|
||||
event.stopPropagation();
|
||||
navbar.dataset.expanded = `${navbar.dataset.expanded === 'false'}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<Flex
|
||||
direction="column"
|
||||
align="center"
|
||||
data-expanded="false"
|
||||
className="navbar"
|
||||
width="100%"
|
||||
top="0"
|
||||
position="sticky"
|
||||
style={{
|
||||
zIndex: 1,
|
||||
backdropFilter: 'blur(4rem)',
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
className="content"
|
||||
width="100%"
|
||||
p={{ initial: '4', sm: '3' }}
|
||||
direction="column"
|
||||
align="center"
|
||||
>
|
||||
<Flex gap="4" direction="column" maxWidth="80rem" width="100%">
|
||||
<Flex align="center">
|
||||
<LinkI18n
|
||||
{locale}
|
||||
color="gray"
|
||||
highContrast
|
||||
href="/"
|
||||
underline="hover"
|
||||
weight="medium"
|
||||
>
|
||||
<Flex align="center" gap="1">
|
||||
BlitzKit
|
||||
{
|
||||
isBranchNamed && (
|
||||
<Code color="gray" size="1" variant="outline">
|
||||
{branchName}
|
||||
</Code>
|
||||
)
|
||||
}
|
||||
</Flex>
|
||||
</LinkI18n>
|
||||
|
||||
<Box flexGrow="1" />
|
||||
|
||||
<Flex
|
||||
align="center"
|
||||
gap={{ initial: '4', md: '6' }}
|
||||
display={{ initial: 'none', sm: 'flex' }}
|
||||
justify="center"
|
||||
className="tools"
|
||||
>
|
||||
{
|
||||
toolsFiltered.map((tool) => {
|
||||
const selected = Astro.url.pathname.includes(`/${tool.id}`);
|
||||
const toolStrings =
|
||||
strings.website.tools[
|
||||
(tool.strings ??
|
||||
tool.id) as keyof typeof strings.website.tools
|
||||
];
|
||||
|
||||
return (
|
||||
<LinkI18n
|
||||
{locale}
|
||||
weight={selected ? 'medium' : 'regular'}
|
||||
color="gray"
|
||||
highContrast={selected}
|
||||
size="2"
|
||||
href={`/${tool.id}`}
|
||||
underline="hover"
|
||||
>
|
||||
{toolStrings.name}
|
||||
</LinkI18n>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Flex>
|
||||
|
||||
<Box flexGrow="1" />
|
||||
|
||||
<Flex align="center" gap="4">
|
||||
<Link
|
||||
style={{ display: 'flex', alignContent: 'center' }}
|
||||
color="gray"
|
||||
href="https://discord.gg/nDt7AjGJQH"
|
||||
underline="none"
|
||||
target="_blank"
|
||||
>
|
||||
<DiscordLogoIcon width="1em" height="1em" />
|
||||
</Link>
|
||||
|
||||
<LinkI18n
|
||||
{locale}
|
||||
style={{ display: 'flex', alignContent: 'center' }}
|
||||
color="gray"
|
||||
href="/settings"
|
||||
underline="none"
|
||||
>
|
||||
<GearIcon width="1em" height="1em" />
|
||||
</LinkI18n>
|
||||
|
||||
<Flex align="center" display={{ initial: 'flex', sm: 'none' }}>
|
||||
<IconButton variant="ghost" color="gray" className="hamburger">
|
||||
<Cross1Icon data-expanded />
|
||||
<HamburgerMenuIcon data-closed />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
direction="column"
|
||||
data-expanded
|
||||
gap="3"
|
||||
display={{ initial: 'flex', sm: 'none' }}
|
||||
>
|
||||
{
|
||||
toolsFiltered.map((tool) => {
|
||||
const toolStrings =
|
||||
strings.website.tools[
|
||||
(tool.strings ?? tool.id) as keyof typeof strings.website.tools
|
||||
];
|
||||
|
||||
return (
|
||||
<Link
|
||||
color="gray"
|
||||
highContrast
|
||||
style={{ display: 'contents' }}
|
||||
href={`/${tool.id}`}
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
borderRadius: Var('radius-2'),
|
||||
backgroundImage: `url(/assets/images/${tool.id}.webp)`,
|
||||
backgroundSize: '200%',
|
||||
backgroundPosition: 'right center',
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
py="4"
|
||||
justify="end"
|
||||
flexGrow="1"
|
||||
style={{
|
||||
background: `linear-gradient(90deg, transparent, ${Var('gray-1')} 90%)`,
|
||||
}}
|
||||
>
|
||||
{toolStrings.name}
|
||||
</Flex>
|
||||
</Box>
|
||||
</Link>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Box
|
||||
data-expanded
|
||||
flexGrow="1"
|
||||
width="100%"
|
||||
display={{ initial: 'block', sm: 'none' }}
|
||||
style={{
|
||||
backgroundColor: Var('black-a7'),
|
||||
backdropFilter: 'blur(0.5rem)',
|
||||
WebkitBackdropFilter: 'blur(0.5rem)',
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Flex, type FlexProps, Theme } from '@radix-ui/themes';
|
||||
import type { ComponentProps } from 'react';
|
||||
import { BlitzKitTheme } from './BlitzKitTheme';
|
||||
|
||||
type PageWrapperProps = FlexProps & {
|
||||
color?: ComponentProps<typeof Theme>['accentColor'];
|
||||
containerProps?: ComponentProps<typeof Theme>;
|
||||
noFlex1?: boolean;
|
||||
noMinHeight?: boolean;
|
||||
};
|
||||
|
||||
export function PageWrapper({
|
||||
style,
|
||||
color,
|
||||
children,
|
||||
noFlex1 = false,
|
||||
containerProps,
|
||||
noMinHeight = false,
|
||||
...props
|
||||
}: PageWrapperProps) {
|
||||
return (
|
||||
<BlitzKitTheme
|
||||
accentColor={color}
|
||||
style={{
|
||||
flex: noFlex1 ? undefined : 1,
|
||||
display: 'flex',
|
||||
minHeight: noMinHeight ? 'unset' : 'calc(100svh - 3rem)',
|
||||
flexDirection: 'column',
|
||||
...containerProps?.style,
|
||||
}}
|
||||
{...containerProps}
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
gap="8"
|
||||
px="4"
|
||||
py="8"
|
||||
width="100%"
|
||||
maxWidth="80rem"
|
||||
style={{
|
||||
margin: 'auto',
|
||||
boxSizing: 'border-box',
|
||||
flex: 1,
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
</BlitzKitTheme>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
import { assertSecret } from '@blitzkit/core';
|
||||
|
||||
const PUBLIC_GOOGLE_TAG_MANAGER_ID = assertSecret(
|
||||
import.meta.env.PUBLIC_GOOGLE_TAG_MANAGER_ID,
|
||||
);
|
||||
---
|
||||
|
||||
{/* google analytics */}
|
||||
<script
|
||||
is:inline
|
||||
async
|
||||
type="text/partytown"
|
||||
src={`https://www.googletagmanager.com/gtag/js?id=${PUBLIC_GOOGLE_TAG_MANAGER_ID}`}
|
||||
></script>
|
||||
<script
|
||||
is:inline
|
||||
define:vars={{ PUBLIC_GOOGLE_TAG_MANAGER_ID }}
|
||||
type="text/partytown"
|
||||
>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag('js', new Date());
|
||||
gtag('config', PUBLIC_GOOGLE_TAG_MANAGER_ID);
|
||||
</script>
|
||||
|
||||
{/* nitro ads */}
|
||||
{
|
||||
/*
|
||||
<script is:inline data-cfasync="false">
|
||||
window.nitroAds = window.nitroAds || {
|
||||
createAd: function () {
|
||||
return new Promise((e) => {
|
||||
window.nitroAds.queue.push(['createAd', arguments, e]);
|
||||
});
|
||||
},
|
||||
addUserToken: function () {
|
||||
window.nitroAds.queue.push(['addUserToken', arguments]);
|
||||
},
|
||||
queue: [],
|
||||
};
|
||||
</script>
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { StandardReward } from '@protos/blitz_static_standard_reward';
|
||||
import { Badge, type BadgeProps } from '@radix-ui/themes';
|
||||
import { useLocale } from '../hooks/useLocale';
|
||||
|
||||
interface RewardBadgeProps {
|
||||
reward: StandardReward;
|
||||
}
|
||||
|
||||
export function RewardBadge({
|
||||
reward,
|
||||
...props
|
||||
}: RewardBadgeProps & BadgeProps) {
|
||||
const { locale } = useLocale();
|
||||
|
||||
if (reward.reward_list.length !== 1) {
|
||||
throw new Error('reward_list must be length 1');
|
||||
}
|
||||
|
||||
const reward0 = reward.reward_list[0];
|
||||
|
||||
Object.entries(reward0).forEach(([key, value]) => {
|
||||
if (key !== 'currency_reward' && value !== undefined) {
|
||||
throw new Error(`unsupported reward type: ${key}`);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Badge {...props}>
|
||||
{reward0.currency_reward && (
|
||||
<>
|
||||
<img
|
||||
src={`/api/currencies/${reward0.currency_reward.currency_catalog_id.replace(
|
||||
'CurrencyEntity.',
|
||||
'',
|
||||
)}.webp`}
|
||||
/>
|
||||
{reward0.currency_reward.amount.toLocaleString(locale)}
|
||||
</>
|
||||
)}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
---
|
||||
import { CaretRightIcon } from '@radix-ui/react-icons';
|
||||
import { Box, Button, Flex, Heading, Text } from '@radix-ui/themes';
|
||||
import type { Tool } from '../constants/tools';
|
||||
import { getStrings } from '../core/i18n/getStrings';
|
||||
import { Var } from '../core/radix/var';
|
||||
import { LinkI18n } from './LinkI18n';
|
||||
|
||||
interface Props {
|
||||
tool: Tool;
|
||||
locale: string;
|
||||
}
|
||||
|
||||
const { tool, locale } = Astro.props;
|
||||
const strings = await getStrings(locale);
|
||||
const disabled = false;
|
||||
const toolStrings =
|
||||
strings.website.tools[
|
||||
(tool.strings ?? tool.id) as keyof typeof strings.website.tools
|
||||
];
|
||||
---
|
||||
|
||||
<Flex
|
||||
style={{
|
||||
backgroundImage: `url(/assets/tool-backgrounds/${tool.id}.webp)`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
borderRadius: Var('radius-2'),
|
||||
}}
|
||||
height={tool.significant ? '20rem' : '16rem'}
|
||||
data-disabled={disabled}
|
||||
data-significant={tool.significant}
|
||||
position="relative"
|
||||
overflow="hidden"
|
||||
flexGrow="1"
|
||||
flexBasis={tool.significant ? undefined : { initial: undefined, sm: '0' }}
|
||||
>
|
||||
<LinkI18n
|
||||
{locale}
|
||||
className="link"
|
||||
underline="none"
|
||||
href={disabled ? '#' : `/${tool.id}`}
|
||||
style={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
flexDirection: 'column',
|
||||
color: 'inherit',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
flexGrow="1"
|
||||
style={{
|
||||
backgroundImage: `url(/assets/tool-backgrounds/${tool.id}.webp)`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Flex
|
||||
px={{ initial: '5', md: tool.significant ? '8' : '6' }}
|
||||
py={{ initial: '4', sm: tool.significant ? '6' : '4' }}
|
||||
gap="4"
|
||||
align="center"
|
||||
justify="between"
|
||||
width="100%"
|
||||
direction={{ initial: 'column', xs: 'row' }}
|
||||
style={{
|
||||
backgroundColor: 'var(--color-panel-translucent)',
|
||||
backdropFilter: 'blur(64px)',
|
||||
WebkitBackdropFilter: 'blur(64px)',
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
justify="center"
|
||||
align={{ initial: 'center', xs: 'start' }}
|
||||
>
|
||||
<Heading
|
||||
align={{ initial: 'center', sm: 'left' }}
|
||||
size={{ initial: '6', sm: tool.significant ? '7' : '5' }}
|
||||
weight="medium"
|
||||
>
|
||||
{toolStrings.name}
|
||||
</Heading>
|
||||
<Text
|
||||
align={{ initial: 'center', sm: 'left' }}
|
||||
size={{ initial: '3', sm: tool.significant ? '4' : '3' }}
|
||||
color="gray"
|
||||
>
|
||||
{toolStrings.description}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
{
|
||||
!disabled && (
|
||||
<Button
|
||||
highContrast={tool.button.contrast}
|
||||
size={{
|
||||
initial: undefined,
|
||||
sm: tool.significant ? '3' : undefined,
|
||||
}}
|
||||
color={tool.button.color}
|
||||
style={{ cursor: 'inherit' }}
|
||||
>
|
||||
{toolStrings.button}
|
||||
<CaretRightIcon />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
</Flex>
|
||||
</LinkI18n>
|
||||
</Flex>
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
import { AstroSeo } from '@astrolib/seo';
|
||||
import { type Tool } from '../constants/tools';
|
||||
import { getStrings } from '../core/i18n/getStrings';
|
||||
|
||||
interface Props {
|
||||
tool: Tool;
|
||||
locale: string;
|
||||
}
|
||||
|
||||
const { tool, locale } = Astro.props;
|
||||
const strings = await getStrings(locale);
|
||||
const toolStrings =
|
||||
strings.website.tools[
|
||||
(tool.strings ?? tool.id) as keyof typeof strings.website.tools
|
||||
];
|
||||
---
|
||||
|
||||
<AstroSeo
|
||||
title={toolStrings.name}
|
||||
description={toolStrings.seo_description}
|
||||
canonical={Astro.url.href}
|
||||
openGraph={{
|
||||
url: Astro.url.href,
|
||||
images: [
|
||||
{ url: `${Astro.url.origin}/assets/images/tools/${tool.id}.webp` },
|
||||
],
|
||||
title: toolStrings.name,
|
||||
description: toolStrings.seo_description,
|
||||
site_name: 'BlitzKit',
|
||||
}}
|
||||
twitter={{ cardType: 'summary_large_image' }}
|
||||
/>
|
||||
@@ -0,0 +1,85 @@
|
||||
import type { ButtonProps } from '@radix-ui/themes';
|
||||
|
||||
export interface Tool {
|
||||
id: string;
|
||||
enabled?: boolean;
|
||||
strings?: string;
|
||||
significant?: boolean;
|
||||
button: { color: ButtonProps['color']; contrast?: boolean };
|
||||
}
|
||||
|
||||
export const tanksTool: Tool = {
|
||||
id: 'tanks',
|
||||
strings: 'tankopedia',
|
||||
significant: true,
|
||||
button: { color: 'purple' },
|
||||
};
|
||||
|
||||
export const compareTool: Tool = {
|
||||
id: 'compare',
|
||||
button: { color: 'crimson' },
|
||||
};
|
||||
|
||||
export const performanceTool: Tool = {
|
||||
id: 'performance',
|
||||
button: { color: 'jade' },
|
||||
};
|
||||
|
||||
export const guessTool: Tool = {
|
||||
id: 'guess',
|
||||
button: { color: 'bronze' },
|
||||
};
|
||||
|
||||
export const chartsTool: Tool = {
|
||||
id: 'charts',
|
||||
button: { color: 'bronze' },
|
||||
};
|
||||
|
||||
export const avatarsTool: Tool = {
|
||||
id: 'avatars',
|
||||
enabled: true,
|
||||
significant: true,
|
||||
button: { color: 'gold' },
|
||||
};
|
||||
|
||||
export const sessionTool: Tool = {
|
||||
id: 'session',
|
||||
button: { color: 'blue' },
|
||||
};
|
||||
|
||||
export const tierListTool: Tool = {
|
||||
id: 'tier-list',
|
||||
strings: 'tier_list',
|
||||
button: { color: 'orange' },
|
||||
};
|
||||
|
||||
export const embedTool: Tool = {
|
||||
id: 'embed',
|
||||
button: { color: 'cyan' },
|
||||
};
|
||||
|
||||
export const ratingTool: Tool = {
|
||||
id: 'rating',
|
||||
button: { color: 'orange' },
|
||||
};
|
||||
|
||||
export const internalTool: Tool = {
|
||||
id: 'internal',
|
||||
enabled: true,
|
||||
button: { color: 'amber' },
|
||||
};
|
||||
|
||||
export const tools: Tool[] = [
|
||||
tanksTool,
|
||||
compareTool,
|
||||
performanceTool,
|
||||
guessTool,
|
||||
chartsTool,
|
||||
avatarsTool,
|
||||
sessionTool,
|
||||
tierListTool,
|
||||
embedTool,
|
||||
ratingTool,
|
||||
];
|
||||
|
||||
if (import.meta.env.DEV) tools.push(internalTool);
|
||||
@@ -0,0 +1,3 @@
|
||||
import { BlitzkitClientAPI } from '@blitzkit/core';
|
||||
|
||||
export const api = new BlitzkitClientAPI();
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { BlitzkitAPI } from 'packages/core/src';
|
||||
|
||||
let api: BlitzkitAPI;
|
||||
|
||||
if (import.meta.env.SSR) {
|
||||
api = await import('./server').then((module) => module.api);
|
||||
} else {
|
||||
api = await import('./client').then((module) => module.api);
|
||||
}
|
||||
|
||||
export { api };
|
||||
@@ -0,0 +1,12 @@
|
||||
import { fetchMetadata } from '@blitzkit/closed';
|
||||
import {
|
||||
assertSecret,
|
||||
BlitzkitServerAPI,
|
||||
MetadataAccessor,
|
||||
} from '@blitzkit/core';
|
||||
|
||||
const metadata = await fetchMetadata(
|
||||
assertSecret(import.meta.env.AUTO_PROXY_ORIGIN),
|
||||
);
|
||||
const accessor = new MetadataAccessor(metadata);
|
||||
export const api = new BlitzkitServerAPI(accessor);
|
||||
@@ -0,0 +1,10 @@
|
||||
import { prerelease } from 'semver';
|
||||
import packageJSON from '../../../../../package.json';
|
||||
|
||||
export function resolveBranchName() {
|
||||
const components = prerelease(packageJSON.version);
|
||||
|
||||
if (components === null) return undefined;
|
||||
|
||||
return components[0];
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Library, types } from 'ffi-napi';
|
||||
import { assertSecret } from 'packages/core/src';
|
||||
|
||||
export const FFI_PATH =
|
||||
'../../src/BlitzKit.FFI/bin/Release/net9.0/win-x64/publish/BlitzKit.FFI.dll';
|
||||
|
||||
export const ffi = Library(FFI_PATH, {
|
||||
debug_echo: [types.CString, [types.CString]],
|
||||
mount_game: ['void', [types.CString]],
|
||||
debug_get_file_count: [types.int, []],
|
||||
get_bindings_url_production: [types.CString, []],
|
||||
get_bindings_url_dev: [types.CString, []],
|
||||
});
|
||||
|
||||
ffi.mount_game(assertSecret(import.meta.env.GAME_MOUNT_POINT));
|
||||
@@ -0,0 +1,15 @@
|
||||
import locales from '@blitzkit/i18n/locales.json' with { type: 'json' };
|
||||
|
||||
export function getBestLocale() {
|
||||
const desiredLocale =
|
||||
localStorage.getItem('preferred-locale') ??
|
||||
navigator.language.split('-')[0];
|
||||
const isSupported = locales.supported.some(
|
||||
({ locale }) => locale === desiredLocale,
|
||||
);
|
||||
const isDefault = desiredLocale === locales.default;
|
||||
|
||||
if (!isSupported || isDefault) return undefined;
|
||||
|
||||
return desiredLocale;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import en from '@strings/en.json' with { type: 'json' };
|
||||
import { merge } from 'lodash-es';
|
||||
import type { BlitzKitStrings } from 'packages/i18n/src';
|
||||
|
||||
export async function getStrings(locale: string) {
|
||||
const strings = await import(`../../../../i18n/strings/${locale}.json`);
|
||||
return merge({}, en, strings) as BlitzKitStrings;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import * as ColorsRaw from '@radix-ui/colors';
|
||||
|
||||
type ExcludeSuffix<T> =
|
||||
T extends `${string}${'A' | `${'P3' | 'Dark'}${'' | 'A'}`}` ? never : T;
|
||||
export type RadixColor = ExcludeSuffix<keyof typeof ColorsRaw>;
|
||||
|
||||
export type VarName =
|
||||
| `color-${'background' | `panel-${'solid' | 'translucent'}` | 'surface' | 'overlay'}`
|
||||
| `radius-${1 | 2 | 3 | 4 | 5 | 6 | 'full'}`
|
||||
| `space-${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`
|
||||
| `shadow-${1 | 2 | 3 | 4 | 5 | 6}`
|
||||
| `${RadixColor | 'accent' | 'gray' | 'black' | 'white'}-${
|
||||
| `${'' | 'a'}${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12}`
|
||||
| 'surface'
|
||||
| 'indicator'
|
||||
| 'track'
|
||||
| 'contrast'}`;
|
||||
|
||||
export function Var(name: VarName) {
|
||||
return `var(--${name})`;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
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 | undefined>(undefined);
|
||||
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 };
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/// <reference path="../.astro/types.d.ts" />
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useEffect, useRef, type RefObject } from 'react';
|
||||
|
||||
interface UseIntersectionOptions extends IntersectionObserverInit {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
export function useIntersection(
|
||||
callback: () => void,
|
||||
ref: RefObject<HTMLElement | null>,
|
||||
options?: UseIntersectionOptions,
|
||||
) {
|
||||
const observer = useRef<IntersectionObserver>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (options?.disabled || !ref.current) return;
|
||||
|
||||
const rect = ref.current.getBoundingClientRect();
|
||||
const visible = !(rect.bottom < 0 || rect.top - window.innerHeight >= 0);
|
||||
|
||||
if (visible) callback();
|
||||
|
||||
observer.current = new IntersectionObserver(callback, options);
|
||||
observer.current.observe(ref.current);
|
||||
|
||||
return () => observer.current!.disconnect();
|
||||
}, []);
|
||||
|
||||
return observer;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import locales from '@blitzkit/i18n/locales.json' with { type: 'json' };
|
||||
import type { BlitzKitStrings } from 'packages/i18n/src';
|
||||
import { createContext, use, useContext } from 'react';
|
||||
const LocaleContext = createContext<{
|
||||
locale: string;
|
||||
gameStrings: Record<string, string>;
|
||||
} | null>(null);
|
||||
|
||||
export interface LocaleAcceptorProps {
|
||||
localeData: {
|
||||
locale: string;
|
||||
gameStrings?: Record<string, string>;
|
||||
};
|
||||
}
|
||||
|
||||
interface LocaleProviderProps extends LocaleAcceptorProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function LocaleProvider({ localeData, children }: LocaleProviderProps) {
|
||||
const isSupported = locales.supported.some(
|
||||
(supported) => supported.locale === localeData.locale,
|
||||
);
|
||||
|
||||
if (!isSupported) throw new Error(`Unsupported locale: ${localeData.locale}`);
|
||||
|
||||
return (
|
||||
<LocaleContext.Provider
|
||||
value={{
|
||||
locale: localeData.locale,
|
||||
gameStrings: localeData.gameStrings ?? {},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</LocaleContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const strings = import.meta.glob('../../../i18n/strings/*.json', {
|
||||
import: 'default',
|
||||
});
|
||||
const cache = new Map<string, Promise<BlitzKitStrings>>();
|
||||
|
||||
function loadLocale(locale: string) {
|
||||
if (cache.has(locale)) return cache.get(locale)!;
|
||||
|
||||
let promise: Promise<BlitzKitStrings>;
|
||||
|
||||
if (import.meta.env.SSR) {
|
||||
promise = import('../core/i18n/getStrings').then((module) =>
|
||||
module.getStrings(locale),
|
||||
);
|
||||
} else {
|
||||
promise = fetch(`/api/strings/${locale}.json`).then((response) =>
|
||||
response.json(),
|
||||
);
|
||||
}
|
||||
|
||||
cache.set(locale, promise);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
export function useLocale() {
|
||||
const localeData = useContext(LocaleContext);
|
||||
|
||||
if (localeData === null) {
|
||||
throw new Error('useLocale must be used within a LocaleProvider');
|
||||
}
|
||||
|
||||
const strings = use(loadLocale(localeData.locale));
|
||||
|
||||
return { ...localeData, strings };
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
import { Flex } from '@radix-ui/themes';
|
||||
import { BlitzKitTheme } from '../components/BlitzKitTheme';
|
||||
import { Checks } from '../components/Checks';
|
||||
import Footer from '../components/Footer.astro';
|
||||
import Navbar from '../components/Navbar.astro';
|
||||
import Party3 from '../components/Party3.astro';
|
||||
|
||||
export interface Props {
|
||||
noFooter?: boolean;
|
||||
noNavbar?: boolean;
|
||||
integrated?: boolean;
|
||||
locale: string;
|
||||
}
|
||||
|
||||
const { noFooter, noNavbar, integrated, locale } = Astro.props;
|
||||
const localeData = { locale };
|
||||
---
|
||||
|
||||
<html lang={locale}>
|
||||
<head>
|
||||
<meta property="og:site_name" content="BlitzKit" />
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||
|
||||
<Party3 />
|
||||
|
||||
<script>
|
||||
import locales from '@blitzkit/i18n/locales.json' with { type: 'json' };
|
||||
import { getBestLocale } from '../core/i18n/getBestLocale';
|
||||
const isAPI = window.location.pathname.startsWith('/api');
|
||||
const isI18nPath = locales.supported.some(({ locale }) =>
|
||||
window.location.pathname.startsWith(`/${locale}`),
|
||||
);
|
||||
const bestLocale = getBestLocale();
|
||||
const currentPathLocale = locales.supported.find(({ locale }) =>
|
||||
window.location.pathname.startsWith(`/${locale}`),
|
||||
);
|
||||
|
||||
if (
|
||||
!isAPI && // not an api, duh!
|
||||
localStorage.getItem('preferred-locale') !== null && // no preference, so probably google's crawler
|
||||
currentPathLocale &&
|
||||
bestLocale !== currentPathLocale.locale // we need to redirect
|
||||
) {
|
||||
window.location.pathname = isI18nPath
|
||||
? window.location.pathname.replace(
|
||||
`/${currentPathLocale.locale}`,
|
||||
bestLocale === undefined ? '' : `/${bestLocale}`,
|
||||
)
|
||||
: `/${bestLocale}${window.location.pathname}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style is:global>
|
||||
@import '@radix-ui/themes/styles.css';
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<slot name="head" />
|
||||
</head>
|
||||
|
||||
<body class={noNavbar ? 'no-navbar' : ''}>
|
||||
<BlitzKitTheme>
|
||||
<Flex direction="column" minHeight="100vh">
|
||||
<Navbar {locale} {integrated} />
|
||||
|
||||
<Checks {localeData} client:only="react" />
|
||||
|
||||
<slot />
|
||||
|
||||
{!noFooter && <Footer {locale} />}
|
||||
</Flex>
|
||||
</BlitzKitTheme>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
import locales from '@blitzkit/i18n/locales.json';
|
||||
import DeniedInternal from '../components/DeniedInternal.astro';
|
||||
import BaseLayout from './BaseLayout.astro';
|
||||
|
||||
const locale = locales.default;
|
||||
const isInternal = import.meta.env.DEV;
|
||||
---
|
||||
|
||||
{
|
||||
isInternal ? (
|
||||
<BaseLayout {locale}>
|
||||
<slot />
|
||||
</BaseLayout>
|
||||
) : (
|
||||
<DeniedInternal />
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
import locales from '@blitzkit/i18n/locales.json';
|
||||
import DeniedInternal from '../components/DeniedInternal.astro';
|
||||
import type { Tool } from '../constants/tools';
|
||||
import ToolLayout from './ToolLayout.astro';
|
||||
|
||||
interface Props {
|
||||
tool: Tool;
|
||||
}
|
||||
|
||||
const { tool } = Astro.props;
|
||||
const locale = locales.default;
|
||||
const isInternal = import.meta.env.DEV;
|
||||
---
|
||||
|
||||
{
|
||||
isInternal ? (
|
||||
<ToolLayout {tool} {locale}>
|
||||
<slot />
|
||||
</ToolLayout>
|
||||
) : (
|
||||
<DeniedInternal />
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
|
||||
import ToolOpenGraph from '../components/ToolOpenGraph.astro';
|
||||
import { type Tool } from '../constants/tools';
|
||||
import { getStrings } from '../core/i18n/getStrings';
|
||||
import BaseLayout from './BaseLayout.astro';
|
||||
|
||||
interface Props {
|
||||
tool: Tool;
|
||||
locale: string;
|
||||
}
|
||||
|
||||
const { tool, locale } = Astro.props;
|
||||
const strings = await getStrings(locale);
|
||||
const toolStrings =
|
||||
strings.website.tools[
|
||||
(tool.strings ?? tool.id) as keyof typeof strings.website.tools
|
||||
];
|
||||
---
|
||||
|
||||
<ToolOpenGraph {locale} tool={tool} />
|
||||
|
||||
<BaseLayout {locale}>
|
||||
<Box
|
||||
style={{
|
||||
backgroundImage: `url(/assets/tool-backgrounds/${tool.id}.webp)`,
|
||||
backgroundSize: 'cover',
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
align="center"
|
||||
py="6"
|
||||
style={{
|
||||
backdropFilter: 'blur(4rem)',
|
||||
WebkitBackdropFilter: 'blur(4rem)',
|
||||
}}
|
||||
>
|
||||
<Heading size="7">{toolStrings.name}</Heading>
|
||||
<Text>{toolStrings.description}</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
<slot />
|
||||
</BaseLayout>
|
||||