Compare commits
2 Commits
2ee8cd6be3
...
89faa7cd40
| Author | SHA1 | Date | |
|---|---|---|---|
| 89faa7cd40 | |||
| a800c7fff7 |
@ -87,15 +87,25 @@ def get_results(request: HttpRequest):
|
||||
)
|
||||
|
||||
# Build response with all objectives and compute total score
|
||||
objectives_with_points = []
|
||||
total_score = 0
|
||||
with_points = {}
|
||||
for obj in ObjectivPoint.objects.all():
|
||||
with_points[obj.objectiv_id] = {
|
||||
"objectiv_id": obj.objectiv_id,
|
||||
"display_string": obj.display_string,
|
||||
"count": 0,
|
||||
"max_count": obj.max_count,
|
||||
"points_per_objectiv": obj.point,
|
||||
"total_points": 0,
|
||||
"first_seen_at": None,
|
||||
"seed": None,
|
||||
}
|
||||
|
||||
for obj in user_objectives.order_by("-total_points"):
|
||||
points = obj["total_points"] or 0
|
||||
objectives_with_points.append(
|
||||
with_points[obj["objectiv_id"]].update(
|
||||
{
|
||||
"objectiv_id": obj["objectiv_id"],
|
||||
"count": obj["count"],
|
||||
"points_per_objectiv": obj["points_per_objectiv"] or 0,
|
||||
"total_points": points,
|
||||
"first_seen_at": obj["first_seen_at"],
|
||||
"seed": obj["seed"],
|
||||
@ -109,7 +119,7 @@ def get_results(request: HttpRequest):
|
||||
data = {
|
||||
"total_score": total_score,
|
||||
"deaths_count": deaths_count,
|
||||
"objectives": objectives_with_points,
|
||||
"objectives": list(with_points.values()),
|
||||
}
|
||||
|
||||
cache.set(f"api:noita:results:{request.user.id}", data, 300)
|
||||
|
||||
@ -15,10 +15,13 @@ class NoitaSubmissionOut(Schema):
|
||||
|
||||
class ObjectivResultOut(Schema):
|
||||
objectiv_id: str
|
||||
first_seen_at: datetime
|
||||
seed: str
|
||||
display_string: str
|
||||
first_seen_at: datetime | None
|
||||
count: int
|
||||
max_count: int
|
||||
seed: str | None
|
||||
points_per_objectiv: int
|
||||
total_points: int
|
||||
total_points: int | None
|
||||
|
||||
|
||||
class ResultsOut(Schema):
|
||||
|
||||
@ -14,8 +14,11 @@ import {
|
||||
|
||||
interface Objective {
|
||||
objectiv_id: string;
|
||||
first_seen_at: string;
|
||||
seed: string;
|
||||
display_string: string;
|
||||
first_seen_at: string | null;
|
||||
count: number;
|
||||
max_count: number;
|
||||
seed: string | null;
|
||||
points_per_objectiv: number;
|
||||
total_points: number;
|
||||
}
|
||||
@ -40,23 +43,21 @@ const columnHelper = createColumnHelper<Objective>();
|
||||
const sorting = ref<SortingState>([]);
|
||||
const columnFilters = ref<ColumnFiltersState>([]);
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const formatDate = (dateString: string | null) => {
|
||||
if (!dateString) {
|
||||
return ""
|
||||
}
|
||||
const date = dayjs(dateString);
|
||||
return date.format("MMM DD, YYYY HH:mm");
|
||||
};
|
||||
|
||||
const getDateTooltip = (dateString: string) => {
|
||||
const date = dayjs(dateString);
|
||||
return date.format("dddd, MMMM D, YYYY [at] h:mm A");
|
||||
};
|
||||
|
||||
const columns = [
|
||||
columnHelper.accessor("objectiv_id", {
|
||||
header: "Objective ID",
|
||||
cell: (info) => info.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("total_points", {
|
||||
header: "Total Points",
|
||||
header: "Your points",
|
||||
cell: (info) => info.getValue() || 0,
|
||||
}),
|
||||
columnHelper.accessor("first_seen_at", {
|
||||
@ -65,6 +66,12 @@ const columns = [
|
||||
sortingFn: (rowA, rowB) => {
|
||||
const dateA = dayjs(rowA.original.first_seen_at);
|
||||
const dateB = dayjs(rowB.original.first_seen_at);
|
||||
if (!rowA.original.first_seen_at) {
|
||||
return rowB.original.first_seen_at ? 1 : 0
|
||||
}
|
||||
if (!rowB.original.first_seen_at) {
|
||||
return rowA.original.first_seen_at ? 0 : 1
|
||||
}
|
||||
return dateA.isBefore(dateB) ? -1 : dateA.isAfter(dateB) ? 1 : 0;
|
||||
},
|
||||
}),
|
||||
@ -104,9 +111,7 @@ const table = computed(() =>
|
||||
const itemData = row.getValue(columnId);
|
||||
const searchValue = value.toLowerCase();
|
||||
if (columnId === "first_seen_at") {
|
||||
const dateStr = itemData as string;
|
||||
const formatted = dayjs(dateStr).format("MMM DD, YYYY HH:mm");
|
||||
return formatted.toLowerCase().includes(searchValue);
|
||||
return formatDate(itemData as string).includes(searchValue)
|
||||
}
|
||||
return String(itemData).toLowerCase().includes(searchValue);
|
||||
},
|
||||
@ -284,7 +289,7 @@ onMounted(() => {
|
||||
<div class="min-h-screen bg-base-200">
|
||||
<!-- Header -->
|
||||
<div class="navbar bg-base-100 shadow-lg">
|
||||
<div class="container mx-auto w-full flex items-center gap-4">
|
||||
<div class="container min-w-3/4 mx-auto w-full flex items-center gap-4">
|
||||
<button @click="goHome" class="btn btn-primary btn-sm">
|
||||
<i class="mdi mdi-arrow-left"></i>
|
||||
Back
|
||||
@ -297,14 +302,16 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="container min-w-3/4 mx-auto px-4 py-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<!-- Left Column: User Ranking -->
|
||||
<div class="lg:col-span-1">
|
||||
<div class="card bg-base-100 shadow-lg sticky top-8">
|
||||
<div class="bg-gradient-to-br from-purple-600 to-purple-400 p-8 text-white rounded-t-2xl">
|
||||
<i class="mdi mdi-trophy text-5xl"></i>
|
||||
<h2 class="text-3xl font-bold mt-3">Your Ranking</h2>
|
||||
<h2 class="text-3xl font-bold">
|
||||
<i class="mdi mdi-trophy text-3xl"></i>
|
||||
Your Ranking
|
||||
</h2>
|
||||
</div>
|
||||
<div class="card-body p-8">
|
||||
<div class="text-center mb-8">
|
||||
@ -455,7 +462,7 @@ onMounted(() => {
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-2xl mb-6">
|
||||
<i class="mdi mdi-view-list text-purple-500 mr-2"></i>
|
||||
Your Objectives
|
||||
Objectives
|
||||
</h2>
|
||||
|
||||
<div v-if="objectives.length === 0" class="text-center py-8">
|
||||
@ -509,16 +516,21 @@ onMounted(() => {
|
||||
cell.column.id === 'objectiv_id'
|
||||
? 'font-medium'
|
||||
: 'text-right',
|
||||
cell.column.id === 'total_points' ? 'font-bold text-primary' : '',
|
||||
]">
|
||||
<template v-if="cell.column.id === 'objectiv_id'">
|
||||
<a :href="`https://noita.wiki.gg/wiki/${row.original.objectiv_id}`" target="_blank">
|
||||
{{ row.original.objectiv_id }}
|
||||
{{ row.original.display_string }}
|
||||
<i class="mdi mdi-open-in-new"></i>
|
||||
</a>
|
||||
</template>
|
||||
<template v-else-if="cell.column.id === 'total_points'">
|
||||
<span :class="row.original.count >= row.original.max_count ? 'text-primary' : 'text-error'">
|
||||
{{ row.original.total_points }} / {{ row.original.points_per_objectiv *
|
||||
row.original.max_count }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="cell.column.id === 'first_seen_at'">
|
||||
<span :title="getDateTooltip(row.original.first_seen_at)">
|
||||
<span :title="formatDate(row.original.first_seen_at)">
|
||||
{{ formatDate(row.original.first_seen_at) }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@ -118,7 +118,7 @@ const goHome = () => {
|
||||
<div class="min-h-screen bg-base-200">
|
||||
<!-- Header -->
|
||||
<div class="navbar bg-base-100 shadow-lg">
|
||||
<div class="container mx-auto w-full flex items-center gap-4">
|
||||
<div class="container min-w-3/4 mx-auto w-full flex items-center gap-4">
|
||||
<button @click="goHome" class="btn btn-primary btn-sm">
|
||||
<i class="mdi mdi-arrow-left"></i>
|
||||
Back
|
||||
@ -140,7 +140,7 @@ const goHome = () => {
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="container min-w-3/4 mx-auto px-4 py-8">
|
||||
<!-- Loading State -->
|
||||
<div v-if="userInfo?.is_superuser" class="flex justify-center">
|
||||
<div class="text-center">
|
||||
|
||||
@ -173,9 +173,11 @@ onMounted(() => {
|
||||
<!-- Left Column: Your Ranking -->
|
||||
<div class="lg:col-span-1">
|
||||
<div class="card bg-base-100 shadow-lg sticky top-8">
|
||||
<div class="bg-gradient-to-br from-blue-600 to-blue-400 p-6 text-white rounded-t-2xl">
|
||||
<i class="mdi mdi-trophy text-4xl"></i>
|
||||
<h3 class="text-2xl font-bold mt-2">Your Ranking</h3>
|
||||
<div class="bg-gradient-to-br from-purple-600 to-purple-400 p-6 text-white rounded-t-2xl">
|
||||
<h3 class="text-3xl font-bold">
|
||||
<i class="mdi mdi-trophy text-3xl"></i>
|
||||
Your Ranking
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="text-center mb-6">
|
||||
@ -315,17 +317,17 @@ onMounted(() => {
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="text-center">
|
||||
<span class="text-2xl font-bold text-primary"><small>x</small>{{ puzzle.points_factor.cost
|
||||
}}</span>
|
||||
}}</span>
|
||||
<p class="text-xs text-base-content/70">Cost</p>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<span class="text-2xl font-bold text-primary"><small>x</small>{{ puzzle.points_factor.cycles
|
||||
}}</span>
|
||||
}}</span>
|
||||
<p class="text-xs text-base-content/70">Cycles</p>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<span class="text-2xl font-bold text-primary"><small>x</small>{{ puzzle.points_factor.area
|
||||
}}</span>
|
||||
}}</span>
|
||||
<p class="text-xs text-base-content/70">Area</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1 +1 @@
|
||||
import{k as t,l as a,p as n,v as s}from"./style-CrNkWMsg.js";const c={key:0,class:"flex justify-center"},k={key:0,class:"badge badge-warning badge-lg"},d={key:1,class:"badge badge-lg"},l={key:2,class:"badge badge-lg"},o={key:3,class:"badge badge-lg"},g={key:1,class:"text-2xl text-base-content/50"},y=t({__name:"RankBadge",props:{rank:{}},setup(e){return(i,r)=>e.rank!==null?(n(),a("div",c,[e.rank===1?(n(),a("span",k," 🏆 #"+s(e.rank),1)):e.rank===2?(n(),a("span",d," 🥈 #"+s(e.rank),1)):e.rank===3?(n(),a("span",l," 🥉 #"+s(e.rank),1)):(n(),a("span",o," #"+s(e.rank),1))])):(n(),a("div",g," No rank yet "))}});export{y as _};
|
||||
import{k as t,l as a,p as n,v as s}from"./style-CufywNmO.js";const c={key:0,class:"flex justify-center"},k={key:0,class:"badge badge-warning badge-lg"},d={key:1,class:"badge badge-lg"},l={key:2,class:"badge badge-lg"},o={key:3,class:"badge badge-lg"},g={key:1,class:"text-2xl text-base-content/50"},y=t({__name:"RankBadge",props:{rank:{}},setup(e){return(i,r)=>e.rank!==null?(n(),a("div",c,[e.rank===1?(n(),a("span",k," 🏆 #"+s(e.rank),1)):e.rank===2?(n(),a("span",d," 🥈 #"+s(e.rank),1)):e.rank===3?(n(),a("span",l," 🥉 #"+s(e.rank),1)):(n(),a("span",o," #"+s(e.rank),1))])):(n(),a("div",g," No rank yet "))}});export{y as _};
|
||||
@ -1 +1 @@
|
||||
import{k as b,c as v,r as g,l as a,p as n,s as t,F as h,y as x,v as i,x as f,O as _}from"./style-CrNkWMsg.js";const y={class:"min-h-screen bg-base-300 flex items-center justify-center px-4"},w={class:"w-full max-w-6xl"},k={class:"grid grid-cols-1 md:grid-cols-2 gap-8"},S=["onClick"],I={class:"relative h-60 bg-base-300 overflow-hidden"},j=["src","alt","onError"],E={key:1,class:"w-full h-full bg-gradient-to-br from-blue-600 to-blue-400 flex items-center justify-center text-white"},N={class:"card-body"},C={class:"card-title text-2xl"},B={class:"text-base-content/70"},O=b({__name:"Home",setup(A){const c=v(()=>[{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"}]),r=g(new Set),d=o=>`https://cdn.akamai.steamstatic.com/steam/apps/${o}/header.jpg`,u=o=>{r.value.add(o)},p=o=>{window.location.href=o};return(o,e)=>(n(),a("div",y,[t("div",w,[e[3]||(e[3]=t("div",{class:"text-center mb-12"},[t("h1",{class:"text-5xl font-bold mb-4"},"PolyLAN Submitter"),t("p",{class:"text-xl text-base-content/70"}," Choose a game and submit your best solutions ")],-1)),t("div",k,[(n(!0),a(h,null,x(c.value,s=>(n(),a("div",{key:s.id,onClick:m=>p(s.path),class:"card card-xl bg-base-200 shadow-xl hover:shadow-2xl transition-all cursor-pointer transform hover:-translate-y-2 hover:scale-[1.05] hover:bg-base-100 overflow-hidden"},[t("figure",I,[r.value.has(s.appId)?(n(),a("div",E,[...e[0]||(e[0]=[t("i",{class:"mdi mdi-gamepad-variant text-5xl"},null,-1)])])):(n(),a("img",{key:0,src:d(s.appId),alt:s.title,onError:m=>u(s.appId),class:"w-full h-full object-cover"},null,40,j)),e[1]||(e[1]=t("div",{class:"absolute inset-0 bg-black/30 group-hover:bg-black/20 transition-colors"},null,-1))]),t("div",N,[t("h2",C,i(s.title),1),t("p",B,i(s.description),1),e[2]||(e[2]=t("div",{class:"card-actions justify-end mt-4"},[t("button",{class:"btn btn-primary"},[t("i",{class:"mdi mdi-arrow-right mr-2"}),f(" Submit results ")])],-1))])],8,S))),128))]),e[4]||(e[4]=t("div",{class:"text-center mt-12 text-base-content/50"},[t("p",null,"Select a game above to begin submitting")],-1))])]))}}),l="#app",$=document.querySelector(l),z=_(O,{...$?.dataset});z.mount(l);
|
||||
import{k as b,c as v,r as g,l as a,p as n,s as t,F as h,y as x,v as i,x as f,O as _}from"./style-CufywNmO.js";const y={class:"min-h-screen bg-base-300 flex items-center justify-center px-4"},w={class:"w-full max-w-6xl"},k={class:"grid grid-cols-1 md:grid-cols-2 gap-8"},S=["onClick"],I={class:"relative h-60 bg-base-300 overflow-hidden"},j=["src","alt","onError"],E={key:1,class:"w-full h-full bg-gradient-to-br from-blue-600 to-blue-400 flex items-center justify-center text-white"},N={class:"card-body"},C={class:"card-title text-2xl"},B={class:"text-base-content/70"},O=b({__name:"Home",setup(A){const c=v(()=>[{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"}]),r=g(new Set),d=o=>`https://cdn.akamai.steamstatic.com/steam/apps/${o}/header.jpg`,u=o=>{r.value.add(o)},p=o=>{window.location.href=o};return(o,e)=>(n(),a("div",y,[t("div",w,[e[3]||(e[3]=t("div",{class:"text-center mb-12"},[t("h1",{class:"text-5xl font-bold mb-4"},"PolyLAN Submitter"),t("p",{class:"text-xl text-base-content/70"}," Choose a game and submit your best solutions ")],-1)),t("div",k,[(n(!0),a(h,null,x(c.value,s=>(n(),a("div",{key:s.id,onClick:m=>p(s.path),class:"card card-xl bg-base-200 shadow-xl hover:shadow-2xl transition-all cursor-pointer transform hover:-translate-y-2 hover:scale-[1.05] hover:bg-base-100 overflow-hidden"},[t("figure",I,[r.value.has(s.appId)?(n(),a("div",E,[...e[0]||(e[0]=[t("i",{class:"mdi mdi-gamepad-variant text-5xl"},null,-1)])])):(n(),a("img",{key:0,src:d(s.appId),alt:s.title,onError:m=>u(s.appId),class:"w-full h-full object-cover"},null,40,j)),e[1]||(e[1]=t("div",{class:"absolute inset-0 bg-black/30 group-hover:bg-black/20 transition-colors"},null,-1))]),t("div",N,[t("h2",C,i(s.title),1),t("p",B,i(s.description),1),e[2]||(e[2]=t("div",{class:"card-actions justify-end mt-4"},[t("button",{class:"btn btn-primary"},[t("i",{class:"mdi mdi-arrow-right mr-2"}),f(" Submit results ")])],-1))])],8,S))),128))]),e[4]||(e[4]=t("div",{class:"text-center mt-12 text-base-content/50"},[t("p",null,"Select a game above to begin submitting")],-1))])]))}}),l="#app",$=document.querySelector(l),z=_(O,{...$?.dataset});z.mount(l);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,16 +1,16 @@
|
||||
{
|
||||
"_RankBadge.vue_vue_type_script_setup_true_lang-CzaIkt15.js": {
|
||||
"file": "assets/RankBadge.vue_vue_type_script_setup_true_lang-CzaIkt15.js",
|
||||
"_RankBadge.vue_vue_type_script_setup_true_lang-C5K0DD3W.js": {
|
||||
"file": "assets/RankBadge.vue_vue_type_script_setup_true_lang-C5K0DD3W.js",
|
||||
"name": "RankBadge.vue_vue_type_script_setup_true_lang",
|
||||
"imports": [
|
||||
"_style-CrNkWMsg.js"
|
||||
"_style-CufywNmO.js"
|
||||
]
|
||||
},
|
||||
"_style-CrNkWMsg.js": {
|
||||
"file": "assets/style-CrNkWMsg.js",
|
||||
"_style-CufywNmO.js": {
|
||||
"file": "assets/style-CufywNmO.js",
|
||||
"name": "style",
|
||||
"css": [
|
||||
"assets/style-D95xr4by.css"
|
||||
"assets/style-DaHD49X0.css"
|
||||
],
|
||||
"assets": [
|
||||
"assets/materialdesignicons-webfont-CSr8KVlo.eot",
|
||||
@ -19,9 +19,9 @@
|
||||
"assets/materialdesignicons-webfont-B7mPwVP_.ttf"
|
||||
]
|
||||
},
|
||||
"_style-D95xr4by.css": {
|
||||
"file": "assets/style-D95xr4by.css",
|
||||
"src": "_style-D95xr4by.css"
|
||||
"_style-DaHD49X0.css": {
|
||||
"file": "assets/style-DaHD49X0.css",
|
||||
"src": "_style-DaHD49X0.css"
|
||||
},
|
||||
"node_modules/.pnpm/@mdi+font@7.4.47/node_modules/@mdi/font/fonts/materialdesignicons-webfont.eot": {
|
||||
"file": "assets/materialdesignicons-webfont-CSr8KVlo.eot",
|
||||
@ -40,32 +40,32 @@
|
||||
"src": "node_modules/.pnpm/@mdi+font@7.4.47/node_modules/@mdi/font/fonts/materialdesignicons-webfont.woff2"
|
||||
},
|
||||
"src/home.ts": {
|
||||
"file": "assets/home-CCO6cuKi.js",
|
||||
"file": "assets/home-2m6DwiDu.js",
|
||||
"name": "home",
|
||||
"src": "src/home.ts",
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"_style-CrNkWMsg.js"
|
||||
"_style-CufywNmO.js"
|
||||
]
|
||||
},
|
||||
"src/noita.ts": {
|
||||
"file": "assets/noita-ByhLkfmW.js",
|
||||
"file": "assets/noita-C7qVNYuP.js",
|
||||
"name": "noita",
|
||||
"src": "src/noita.ts",
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"_style-CrNkWMsg.js",
|
||||
"_RankBadge.vue_vue_type_script_setup_true_lang-CzaIkt15.js"
|
||||
"_style-CufywNmO.js",
|
||||
"_RankBadge.vue_vue_type_script_setup_true_lang-C5K0DD3W.js"
|
||||
]
|
||||
},
|
||||
"src/opus-magnum.ts": {
|
||||
"file": "assets/opus_magnum-CBb_5LJ1.js",
|
||||
"file": "assets/opus_magnum-CgBh_a7R.js",
|
||||
"name": "opus_magnum",
|
||||
"src": "src/opus-magnum.ts",
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"_style-CrNkWMsg.js",
|
||||
"_RankBadge.vue_vue_type_script_setup_true_lang-CzaIkt15.js"
|
||||
"_style-CufywNmO.js",
|
||||
"_RankBadge.vue_vue_type_script_setup_true_lang-C5K0DD3W.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user