home page + opus result api working again

This commit is contained in:
Loïc Gremaud 2026-05-10 00:17:20 +02:00
parent 07f5c76a52
commit 19cc52c9f8
Signed by: Legrems
GPG Key ID: D4620E6DF3E0121D
9 changed files with 459 additions and 36 deletions

View File

@ -1,30 +1,36 @@
from collections import defaultdict
from django.http import HttpRequest
from ninja import Router
from accounts.models import CustomUser
from animations.schemas import RankingSchema
from submissions.models import PuzzleResponse, SteamCollectionItem
router = Router()
# @router.get("results", response=RankingSchema)
# def results(request: HttpRequest) -> dict:
# responses_by_userid = defaultdict(list)
# responses_by_puzzleid = defaultdict(list)
#
# for response in list(
# PuzzleResponse.objects.filter(needs_manual_validation=False)
# .filter_user_best_response()
# .prefetch_related("submission__user")
# ):
# responses_by_userid[response.submission.user.id].append(response)
# responses_by_puzzleid[response.puzzle.id].append(response)
#
# ranking = {}
#
# for puzzle_id, responses in responses_by_puzzleid.items():
# ranking[puzzle_id] = sorted(responses, key=lambda x: x.rank_points)
#
# return {
# "users": CustomUser.objects.filter(pk__in=responses_by_userid.keys()),
# "puzzles": SteamCollectionItem.objects.all(),
# "responses_by_userid": responses_by_userid,
# "ranking_by_puzzle": ranking,
# }
@router.get("results", response=RankingSchema)
def results(request: HttpRequest) -> dict:
responses_by_userid = defaultdict(list)
responses_by_puzzleid = defaultdict(list)
for response in list(
PuzzleResponse.objects.filter(needs_manual_validation=False)
.filter_user_best_response()
.prefetch_related("submission__user")
):
responses_by_userid[response.submission.user.id].append(response)
responses_by_puzzleid[response.puzzle.id].append(response)
ranking = {}
for puzzle_id, responses in responses_by_puzzleid.items():
ranking[puzzle_id] = sorted(responses, key=lambda x: x.rank_points)
return {
"users": CustomUser.objects.filter(pk__in=responses_by_userid.keys()),
"puzzles": SteamCollectionItem.objects.all(),
"responses_by_userid": responses_by_userid,
"ranking_by_puzzle": ranking,
}

View File

@ -14,8 +14,8 @@ class PuzzleResponseRankingOut(ModelSchema):
"updated_at",
]
points: int
rank_points: int
points: int | None = None
rank_points: int | None = None
puzzle_user_rank: int
user_response_rank: int

View File

@ -17,7 +17,8 @@
"pinia": "^3.0.3",
"tailwindcss": "^4.1.16",
"tesseract.js": "^5.1.1",
"vue": "^3.5.22"
"vue": "^3.5.22",
"vue-router": "^5.0.6"
},
"devDependencies": {
"@mdi/font": "^7.4.47",

View File

@ -10,7 +10,7 @@ importers:
dependencies:
'@tailwindcss/vite':
specifier: ^4.1.16
version: 4.1.16(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2))
version: 4.1.16(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.4))
'@tanstack/vue-table':
specifier: ^8.21.3
version: 8.21.3(vue@3.5.22(typescript@5.9.3))
@ -32,6 +32,9 @@ importers:
vue:
specifier: ^3.5.22
version: 3.5.22(typescript@5.9.3)
vue-router:
specifier: ^5.0.6
version: 5.0.6(@vue/compiler-sfc@3.5.22)(pinia@3.0.3(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3))
devDependencies:
'@mdi/font':
specifier: ^7.4.47
@ -41,7 +44,7 @@ importers:
version: 24.9.2
'@vitejs/plugin-vue':
specifier: ^6.0.1
version: 6.0.1(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.22(typescript@5.9.3))
version: 6.0.1(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.4))(vue@3.5.22(typescript@5.9.3))
'@vue/tsconfig':
specifier: ^0.8.1
version: 0.8.1(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))
@ -53,13 +56,17 @@ importers:
version: 5.9.3
vite:
specifier: ^7.1.7
version: 7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2)
version: 7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.4)
vue-tsc:
specifier: ^3.1.0
version: 3.1.2(typescript@5.9.3)
packages:
'@babel/generator@7.29.1':
resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==}
engines: {node: '>=6.9.0'}
'@babel/helper-string-parser@7.27.1':
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'}
@ -73,10 +80,19 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/parser@7.29.3':
resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==}
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/types@7.28.5':
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
engines: {node: '>=6.9.0'}
'@babel/types@7.29.0':
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
engines: {node: '>=6.9.0'}
'@esbuild/aix-ppc64@0.25.11':
resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==}
engines: {node: '>=18'}
@ -490,6 +506,15 @@ packages:
'@volar/typescript@2.4.23':
resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==}
'@vue-macros/common@3.1.2':
resolution: {integrity: sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==}
engines: {node: '>=20.19.0'}
peerDependencies:
vue: ^2.7.0 || ^3.2.25
peerDependenciesMeta:
vue:
optional: true
'@vue/compiler-core@3.5.22':
resolution: {integrity: sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==}
@ -505,12 +530,21 @@ packages:
'@vue/devtools-api@7.7.7':
resolution: {integrity: sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==}
'@vue/devtools-api@8.1.2':
resolution: {integrity: sha512-vA0O112YqyDuNA1s7Yb2gCgToQ/OxOWiFDO5ThLCcDy0ldHnSd1dUTaSYhOldbqoNgumE4dxtGAoAaSUKUD1Zg==}
'@vue/devtools-kit@7.7.7':
resolution: {integrity: sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==}
'@vue/devtools-kit@8.1.2':
resolution: {integrity: sha512-f75/upc+GCyjXErpgPGz4582ujS0L/adAltGy+tqXMGUJpgAcfGr6CxnnhpZY8BHuMYt6KpbF8uaFrrQG66rGQ==}
'@vue/devtools-shared@7.7.7':
resolution: {integrity: sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==}
'@vue/devtools-shared@8.1.2':
resolution: {integrity: sha512-X9RyVFYAdkBe4IUf5v48TxBF/6QPmF8CmWrDAjXzfUHrgQ/HGfTC1A6TqgXqZ03ye66l3AD51BAGD69IvKM9sw==}
'@vue/language-core@3.1.2':
resolution: {integrity: sha512-PyFDCqpdfYUT+oMLqcc61oHfJlC6yjhybaefwQjRdkchIihToOEpJ2Wu/Ebq2yrnJdd1EsaAvZaXVAqcxtnDxQ==}
peerDependencies:
@ -560,15 +594,38 @@ packages:
peerDependencies:
vue: ^3.5.0
acorn@8.16.0:
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
engines: {node: '>=0.4.0'}
hasBin: true
alien-signals@3.0.3:
resolution: {integrity: sha512-2JXjom6R7ZwrISpUphLhf4htUq1aKRCennTJ6u9kFfr3sLmC9+I4CxxVi+McoFnIg+p1HnVrfLT/iCt4Dlz//Q==}
ast-kit@2.2.0:
resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==}
engines: {node: '>=20.19.0'}
ast-walker-scope@0.8.3:
resolution: {integrity: sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==}
engines: {node: '>=20.19.0'}
birpc@2.6.1:
resolution: {integrity: sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==}
bmp-js@0.1.0:
resolution: {integrity: sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==}
chokidar@5.0.0:
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
engines: {node: '>= 20.19.0'}
confbox@0.1.8:
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
confbox@0.2.4:
resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==}
copy-anything@4.0.5:
resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==}
engines: {node: '>=18'}
@ -599,6 +656,9 @@ packages:
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
exsolve@1.0.8:
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
@ -640,6 +700,16 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
jsesc@3.1.0:
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
engines: {node: '>=6'}
hasBin: true
json5@2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
hasBin: true
lightningcss-android-arm64@1.30.2:
resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
engines: {node: '>= 12.0.0'}
@ -710,12 +780,23 @@ packages:
resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
engines: {node: '>= 12.0.0'}
local-pkg@1.1.2:
resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==}
engines: {node: '>=14'}
magic-string-ast@1.0.3:
resolution: {integrity: sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==}
engines: {node: '>=20.19.0'}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
mitt@3.0.1:
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
mlly@1.8.2:
resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
muggle-string@0.4.1:
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
@ -740,9 +821,15 @@ packages:
path-browserify@1.0.1:
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
perfect-debounce@1.0.0:
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
perfect-debounce@2.1.0:
resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@ -759,10 +846,23 @@ packages:
typescript:
optional: true
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
pkg-types@2.3.1:
resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==}
postcss@8.5.6:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14}
quansync@0.2.11:
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
readdirp@5.0.0:
resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
engines: {node: '>= 20.19.0'}
regenerator-runtime@0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
@ -774,6 +874,9 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
scule@1.3.0:
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@ -811,9 +914,20 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
ufo@1.6.4:
resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==}
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
unplugin-utils@0.3.1:
resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==}
engines: {node: '>=20.19.0'}
unplugin@3.0.0:
resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==}
engines: {node: ^20.19.0 || >=22.12.0}
vite@7.1.12:
resolution: {integrity: sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==}
engines: {node: ^20.19.0 || >=22.12.0}
@ -857,6 +971,21 @@ packages:
vscode-uri@3.1.0:
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
vue-router@5.0.6:
resolution: {integrity: sha512-9+kmUTGbKMyW9Asoy98IXXYIzrTMT7JDAdpDDeEkorHvybpUvBI2wsrSM5jFOXrFydpzRFJ9vAh+80DN2PGu9w==}
peerDependencies:
'@pinia/colada': '>=0.21.2'
'@vue/compiler-sfc': ^3.5.17
pinia: ^3.0.4
vue: ^3.5.0
peerDependenciesMeta:
'@pinia/colada':
optional: true
'@vue/compiler-sfc':
optional: true
pinia:
optional: true
vue-tsc@3.1.2:
resolution: {integrity: sha512-3fd4DY0rFczs5f+VB3OhcLU83V6+3Puj2yLBe0Ak65k7ERk+STVNKaOAi0EBo6Lc15UiJB6LzU6Mxy4+h/pKew==}
hasBin: true
@ -877,14 +1006,30 @@ packages:
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
webpack-virtual-modules@0.6.2:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
yaml@2.8.4:
resolution: {integrity: sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==}
engines: {node: '>= 14.6'}
hasBin: true
zlibjs@0.3.1:
resolution: {integrity: sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==}
snapshots:
'@babel/generator@7.29.1':
dependencies:
'@babel/parser': 7.29.3
'@babel/types': 7.29.0
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
jsesc: 3.1.0
'@babel/helper-string-parser@7.27.1': {}
'@babel/helper-validator-identifier@7.28.5': {}
@ -893,11 +1038,20 @@ snapshots:
dependencies:
'@babel/types': 7.28.5
'@babel/parser@7.29.3':
dependencies:
'@babel/types': 7.29.0
'@babel/types@7.28.5':
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
'@babel/types@7.29.0':
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
'@esbuild/aix-ppc64@0.25.11':
optional: true
@ -1126,12 +1280,12 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.16
'@tailwindcss/oxide-win32-x64-msvc': 4.1.16
'@tailwindcss/vite@4.1.16(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2))':
'@tailwindcss/vite@4.1.16(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.4))':
dependencies:
'@tailwindcss/node': 4.1.16
'@tailwindcss/oxide': 4.1.16
tailwindcss: 4.1.16
vite: 7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2)
vite: 7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.4)
'@tanstack/table-core@8.21.3': {}
@ -1148,10 +1302,10 @@ snapshots:
'@types/web-bluetooth@0.0.21': {}
'@vitejs/plugin-vue@6.0.1(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.22(typescript@5.9.3))':
'@vitejs/plugin-vue@6.0.1(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.4))(vue@3.5.22(typescript@5.9.3))':
dependencies:
'@rolldown/pluginutils': 1.0.0-beta.29
vite: 7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2)
vite: 7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.4)
vue: 3.5.22(typescript@5.9.3)
'@volar/language-core@2.4.23':
@ -1166,6 +1320,16 @@ snapshots:
path-browserify: 1.0.1
vscode-uri: 3.1.0
'@vue-macros/common@3.1.2(vue@3.5.22(typescript@5.9.3))':
dependencies:
'@vue/compiler-sfc': 3.5.22
ast-kit: 2.2.0
local-pkg: 1.1.2
magic-string-ast: 1.0.3
unplugin-utils: 0.3.1
optionalDependencies:
vue: 3.5.22(typescript@5.9.3)
'@vue/compiler-core@3.5.22':
dependencies:
'@babel/parser': 7.28.5
@ -1200,6 +1364,10 @@ snapshots:
dependencies:
'@vue/devtools-kit': 7.7.7
'@vue/devtools-api@8.1.2':
dependencies:
'@vue/devtools-kit': 8.1.2
'@vue/devtools-kit@7.7.7':
dependencies:
'@vue/devtools-shared': 7.7.7
@ -1210,10 +1378,19 @@ snapshots:
speakingurl: 14.0.1
superjson: 2.2.5
'@vue/devtools-kit@8.1.2':
dependencies:
'@vue/devtools-shared': 8.1.2
birpc: 2.6.1
hookable: 5.5.3
perfect-debounce: 2.1.0
'@vue/devtools-shared@7.7.7':
dependencies:
rfdc: 1.4.1
'@vue/devtools-shared@8.1.2': {}
'@vue/language-core@3.1.2(typescript@5.9.3)':
dependencies:
'@volar/language-core': 2.4.23
@ -1268,12 +1445,32 @@ snapshots:
dependencies:
vue: 3.5.22(typescript@5.9.3)
acorn@8.16.0: {}
alien-signals@3.0.3: {}
ast-kit@2.2.0:
dependencies:
'@babel/parser': 7.28.5
pathe: 2.0.3
ast-walker-scope@0.8.3:
dependencies:
'@babel/parser': 7.28.5
ast-kit: 2.2.0
birpc@2.6.1: {}
bmp-js@0.1.0: {}
chokidar@5.0.0:
dependencies:
readdirp: 5.0.0
confbox@0.1.8: {}
confbox@0.2.4: {}
copy-anything@4.0.5:
dependencies:
is-what: 5.5.0
@ -1322,6 +1519,8 @@ snapshots:
estree-walker@2.0.2: {}
exsolve@1.0.8: {}
fdir@6.5.0(picomatch@4.0.3):
optionalDependencies:
picomatch: 4.0.3
@ -1345,6 +1544,10 @@ snapshots:
jiti@2.6.1: {}
jsesc@3.1.0: {}
json5@2.2.3: {}
lightningcss-android-arm64@1.30.2:
optional: true
@ -1394,12 +1597,29 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.30.2
lightningcss-win32-x64-msvc: 1.30.2
local-pkg@1.1.2:
dependencies:
mlly: 1.8.2
pkg-types: 2.3.1
quansync: 0.2.11
magic-string-ast@1.0.3:
dependencies:
magic-string: 0.30.21
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
mitt@3.0.1: {}
mlly@1.8.2:
dependencies:
acorn: 8.16.0
pathe: 2.0.3
pkg-types: 1.3.1
ufo: 1.6.4
muggle-string@0.4.1: {}
nanoid@3.3.11: {}
@ -1412,8 +1632,12 @@ snapshots:
path-browserify@1.0.1: {}
pathe@2.0.3: {}
perfect-debounce@1.0.0: {}
perfect-debounce@2.1.0: {}
picocolors@1.1.1: {}
picomatch@4.0.3: {}
@ -1425,12 +1649,28 @@ snapshots:
optionalDependencies:
typescript: 5.9.3
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
mlly: 1.8.2
pathe: 2.0.3
pkg-types@2.3.1:
dependencies:
confbox: 0.2.4
exsolve: 1.0.8
pathe: 2.0.3
postcss@8.5.6:
dependencies:
nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1
quansync@0.2.11: {}
readdirp@5.0.0: {}
regenerator-runtime@0.13.11: {}
rfdc@1.4.1: {}
@ -1463,6 +1703,8 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.52.5
fsevents: 2.3.3
scule@1.3.0: {}
source-map-js@1.2.1: {}
speakingurl@14.0.1: {}
@ -1501,9 +1743,22 @@ snapshots:
typescript@5.9.3: {}
ufo@1.6.4: {}
undici-types@7.16.0: {}
vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2):
unplugin-utils@0.3.1:
dependencies:
pathe: 2.0.3
picomatch: 4.0.3
unplugin@3.0.0:
dependencies:
'@jridgewell/remapping': 2.3.5
picomatch: 4.0.3
webpack-virtual-modules: 0.6.2
vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.4):
dependencies:
esbuild: 0.25.11
fdir: 6.5.0(picomatch@4.0.3)
@ -1516,9 +1771,34 @@ snapshots:
fsevents: 2.3.3
jiti: 2.6.1
lightningcss: 1.30.2
yaml: 2.8.4
vscode-uri@3.1.0: {}
vue-router@5.0.6(@vue/compiler-sfc@3.5.22)(pinia@3.0.3(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3)):
dependencies:
'@babel/generator': 7.29.1
'@vue-macros/common': 3.1.2(vue@3.5.22(typescript@5.9.3))
'@vue/devtools-api': 8.1.2
ast-walker-scope: 0.8.3
chokidar: 5.0.0
json5: 2.2.3
local-pkg: 1.1.2
magic-string: 0.30.21
mlly: 1.8.2
muggle-string: 0.4.1
pathe: 2.0.3
picomatch: 4.0.3
scule: 1.3.0
tinyglobby: 0.2.15
unplugin: 3.0.0
unplugin-utils: 0.3.1
vue: 3.5.22(typescript@5.9.3)
yaml: 2.8.4
optionalDependencies:
'@vue/compiler-sfc': 3.5.22
pinia: 3.0.3(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))
vue-tsc@3.1.2(typescript@5.9.3):
dependencies:
'@volar/typescript': 2.4.23
@ -1539,9 +1819,13 @@ snapshots:
webidl-conversions@3.0.1: {}
webpack-virtual-modules@0.6.2: {}
whatwg-url@5.0.0:
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
yaml@2.8.4: {}
zlibjs@0.3.1: {}

View File

@ -1,5 +1,90 @@
<script setup lang="ts">
import { computed, ref } from "vue";
const games = computed(() => [
{
id: "opus-magnum",
title: "Opus Magnum",
description: "Submit your best Opus Magnum puzzle solutions",
appId: 558990,
path: "/opus-magnum",
},
{
id: "noita",
title: "Noita",
description: "Submit your greatest Noita achievements",
appId: 881100,
path: "/noita",
},
]);
const imageErrors = ref<Set<number>>(new Set());
const getHeaderImage = (appId: number) => {
return `https://cdn.akamai.steamstatic.com/steam/apps/${appId}/header.jpg`;
};
const onImageError = (appId: number) => {
imageErrors.value.add(appId);
};
const navigate = (path: string) => {
window.location.href = path;
};
</script>
<template>
<div class="min-h-screen bg-base-200 flex items-center justify-center px-4">
<div class="w-full max-w-4xl">
<!-- Header -->
<div class="text-center mb-12">
<h1 class="text-5xl font-bold mb-4">PolyLAN Submitter</h1>
<p class="text-xl text-base-content/70">
Choose a game and submit your best solutions
</p>
</div>
<!-- Cards Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div
v-for="game in games"
:key="game.id"
@click="navigate(game.path)"
class="card bg-base-100 shadow-xl hover:shadow-2xl transition-all cursor-pointer transform hover:-translate-y-2 overflow-hidden"
>
<figure class="relative h-40 bg-base-300 overflow-hidden">
<img
v-if="!imageErrors.has(game.appId)"
:src="getHeaderImage(game.appId)"
:alt="game.title"
@error="onImageError(game.appId)"
class="w-full h-full object-cover"
/>
<div
v-else
class="w-full h-full bg-gradient-to-br from-blue-600 to-blue-400 flex items-center justify-center text-white"
>
<i class="mdi mdi-gamepad-variant text-5xl"></i>
</div>
<div class="absolute inset-0 bg-black/30 group-hover:bg-black/20 transition-colors"></div>
</figure>
<div class="card-body">
<h2 class="card-title text-2xl">{{ game.title }}</h2>
<p class="text-base-content/70">{{ game.description }}</p>
<div class="card-actions justify-end mt-4">
<button class="btn btn-primary">
<i class="mdi mdi-arrow-right mr-2"></i>
Get Started
</button>
</div>
</div>
</div>
</div>
<!-- Footer -->
<div class="text-center mt-12 text-base-content/50">
<p>Select a game above to begin submitting</p>
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
</script>
<template>
<div class="min-h-screen bg-base-200 flex items-center justify-center px-4">
<div class="w-full max-w-2xl">
<div class="card bg-base-100 shadow-xl">
<div class="bg-gradient-to-br from-purple-600 to-purple-400 p-8 text-white">
<i class="mdi mdi-wand-plus text-6xl"></i>
</div>
<div class="card-body text-center">
<h1 class="card-title text-4xl justify-center">Noita Submitter</h1>
<p class="text-lg text-base-content/70 mt-4">
Coming soon! Noita submission system is under development.
</p>
<div class="card-actions justify-center mt-8">
<a href="/" class="btn btn-primary">
<i class="mdi mdi-arrow-left mr-2"></i>
Back to Home
</a>
</div>
</div>
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,8 @@
import { createApp } from 'vue'
import Noita from '@/Noita.vue'
import '@/style.css'
const selector = "#app"
const mountData = document.querySelector<HTMLElement>(selector)
const app = createApp(Noita, { ...mountData?.dataset })
app.mount(selector)

View File

@ -0,0 +1,13 @@
{% extends "base.html" %}
{% load django_vite %}
{% block title %}Noita Submitter{% endblock %}
{% block head %}
{% vite_asset 'src/noita.ts' %}
{% endblock %}
{% block content %}
<div id="app"></div>
{% endblock %}

View File

@ -2,7 +2,7 @@
{% load django_vite %}
{% block title %}Opus Magnum Puzzle Submitter{% endblock %}
{% block title %}Opus Magnum Submitter{% endblock %}
{% block head %}
{% vite_asset 'src/main.ts' %}