Fix heatmap issue and implement per round analysis
This commit is contained in:
@@ -2,6 +2,8 @@ use leptos::*;
|
||||
use leptos_router::{Outlet, A};
|
||||
|
||||
pub mod heatmap;
|
||||
pub mod scoreboard;
|
||||
pub mod perround;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct CurrentDemoName(ReadSignal<String>);
|
||||
@@ -87,95 +89,3 @@ pub fn demo() -> impl leptos::IntoView {
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[leptos::component]
|
||||
pub fn scoreboard() -> impl leptos::IntoView {
|
||||
use leptos::Suspense;
|
||||
|
||||
let scoreboard_resource =
|
||||
create_resource(leptos_router::use_params_map(), |params| async move {
|
||||
let id = params.get("id").unwrap();
|
||||
|
||||
let res =
|
||||
reqwasm::http::Request::get(&format!("/api/demos/{}/analysis/scoreboard", id))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
res.json::<common::demo_analysis::ScoreBoard>()
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let team_display_func = |team: &[common::demo_analysis::ScoreBoardPlayer]| {
|
||||
team.iter()
|
||||
.map(|player| {
|
||||
view! {
|
||||
<tr>
|
||||
<td> { player.name.clone() } </td>
|
||||
<td> { player.kills } </td>
|
||||
<td> { player.assists } </td>
|
||||
<td> { player.deaths } </td>
|
||||
<td> { player.damage } </td>
|
||||
</tr>
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
view! {
|
||||
<h2>Scoreboard</h2>
|
||||
|
||||
<Suspense
|
||||
fallback=move || view! { <p>Loading Scoreboard data</p> }
|
||||
>
|
||||
<div>
|
||||
<h3>Team 1</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Kills</th>
|
||||
<th>Assists</th>
|
||||
<th>Deaths</th>
|
||||
<th>Damage</th>
|
||||
</tr>
|
||||
{
|
||||
move || {
|
||||
scoreboard_resource.get().map(|s| {
|
||||
let team = s.team1;
|
||||
team_display_func(&team)
|
||||
})
|
||||
}
|
||||
}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>Team 2</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Kills</th>
|
||||
<th>Assists</th>
|
||||
<th>Deaths</th>
|
||||
<th>Damage</th>
|
||||
</tr>
|
||||
{
|
||||
move || {
|
||||
scoreboard_resource.get().map(|s| {
|
||||
let team = s.team2;
|
||||
team_display_func(&team)
|
||||
})
|
||||
}
|
||||
}
|
||||
</table>
|
||||
</div>
|
||||
</Suspense>
|
||||
}
|
||||
}
|
||||
|
||||
#[leptos::component]
|
||||
pub fn per_round() -> impl leptos::IntoView {
|
||||
view! {
|
||||
<h3>Per Round</h3>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,20 +18,28 @@ pub fn heatmaps() -> impl leptos::IntoView {
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
view! {
|
||||
<h3>Heatmaps</h3>
|
||||
|
||||
<Suspense fallback=move || view! { <p>Loading Heatmaps</p> }>
|
||||
<div>
|
||||
{
|
||||
move || {
|
||||
heatmaps_resource.get().map(|h| {
|
||||
view! { <HeatmapView heatmaps=h /> }
|
||||
})
|
||||
}
|
||||
let style = stylers::style! {
|
||||
"Heatmap-Wrapper",
|
||||
.container {
|
||||
margin-top: 1vh;
|
||||
}
|
||||
</div>
|
||||
</Suspense>
|
||||
};
|
||||
|
||||
view! {
|
||||
class=style,
|
||||
<div class="container">
|
||||
<Suspense fallback=move || view! { <p>Loading Heatmaps</p> }>
|
||||
<div>
|
||||
{
|
||||
move || {
|
||||
heatmaps_resource.get().map(|h| {
|
||||
view! { <HeatmapView heatmaps=h /> }
|
||||
})
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</Suspense>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,12 +67,21 @@ fn heatmap_view(heatmaps: Vec<common::demo_analysis::PlayerHeatmap>) -> impl lep
|
||||
.heatmap_image > .heatmap {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.heatmap_image > img {
|
||||
width: min(70vw, 70vh);
|
||||
height: min(70vh, 70vw);
|
||||
}
|
||||
|
||||
.player_select {
|
||||
width: min(70vw, 70vh);
|
||||
}
|
||||
};
|
||||
|
||||
view! {
|
||||
class=style,
|
||||
<div>
|
||||
<select on:change=move |ev| {
|
||||
<select class="player_select" on:change=move |ev| {
|
||||
let new_value = event_target_value(&ev);
|
||||
let idx: usize = new_value.parse().unwrap();
|
||||
set_value(heatmaps.get(idx).cloned());
|
||||
@@ -87,7 +104,7 @@ fn heatmap_view(heatmaps: Vec<common::demo_analysis::PlayerHeatmap>) -> impl lep
|
||||
class=style,
|
||||
<div class="heatmap_image">
|
||||
<img class="radar" src=format!("/static/minimaps/{}.png", map.0.get()) />
|
||||
<img class="heatmap" width=1024 height=1024 src=format!("data:image/png;base64,{}", heatmap.png_data) />
|
||||
<img class="heatmap" src=format!("data:image/png;base64,{}", heatmap.png_data) />
|
||||
</div>
|
||||
}.into_any(),
|
||||
None => view! { <p>ERROR</p> }.into_any(),
|
||||
|
||||
143
frontend/src/demo/perround.rs
Normal file
143
frontend/src/demo/perround.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
use leptos::*;
|
||||
|
||||
fn to_roman(mut number: u32) -> char {
|
||||
if number < 12 {
|
||||
char::from_u32(8544 + number).unwrap()
|
||||
} else if number < 24 {
|
||||
char::from_u32(8544 + (number - 12)).unwrap()
|
||||
} else if number < 27 {
|
||||
char::from_u32(8544 + (number - 24)).unwrap()
|
||||
} else {
|
||||
char::from_u32(8544 + (number - 27)).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn to_coloumn(idx: usize) -> usize {
|
||||
if idx < 12 {
|
||||
1 + idx
|
||||
} else if idx < 24 {
|
||||
1 + idx + 1
|
||||
} else if idx < 27 {
|
||||
1 + idx + 2
|
||||
} else {
|
||||
1 + idx + 3
|
||||
}
|
||||
}
|
||||
|
||||
#[leptos::component]
|
||||
pub fn per_round() -> impl leptos::IntoView {
|
||||
let perround_resource =
|
||||
create_resource(leptos_router::use_params_map(), |params| async move {
|
||||
let id = params.get("id").unwrap();
|
||||
|
||||
let res =
|
||||
reqwasm::http::Request::get(&format!("/api/demos/{}/analysis/perround", id))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
res.json::<Vec<common::demo_analysis::DemoRound>>()
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let style = stylers::style! {
|
||||
"PerRound",
|
||||
.round_overview {
|
||||
display: inline-grid;
|
||||
|
||||
width: 90vw;
|
||||
grid-template-columns: repeat(12, 1fr) 5px repeat(12, 1fr) 5px repeat(3, 1fr) 5px repeat(3, 1fr);
|
||||
grid-template-rows: repeat(3, auto);
|
||||
}
|
||||
|
||||
.round_entry {
|
||||
display: inline-block;
|
||||
|
||||
border-left: 1px solid #101010;
|
||||
border-right: 1px solid #101010;
|
||||
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.round_number {
|
||||
border-top: 1px solid #101010;
|
||||
border-bottom: 1px solid #101010;
|
||||
}
|
||||
|
||||
p.round_entry {
|
||||
margin: 0px;
|
||||
}
|
||||
};
|
||||
|
||||
let (round, set_round) = create_signal(0);
|
||||
|
||||
let events_list = move || {
|
||||
let round_index = round();
|
||||
let current_round = perround_resource.get().map(|rs| rs.get(round_index).cloned()).flatten();
|
||||
|
||||
match current_round {
|
||||
Some(round) => {
|
||||
round.events.iter().map(|event| {
|
||||
match event {
|
||||
common::demo_analysis::RoundEvent::BombPlanted => view! { <li>Bomb has been planted</li> }.into_view(),
|
||||
common::demo_analysis::RoundEvent::BombDefused => view! { <li>Bomb has been defused</li> }.into_view(),
|
||||
common::demo_analysis::RoundEvent::Killed { attacker, died } => view! { <li>{"'"}{ attacker }{"'"} killed {"'"}{ died }{"'"}</li> }.into_view(),
|
||||
}
|
||||
}).collect::<Vec<_>>().into_view()
|
||||
}
|
||||
None => view! {}.into_view(),
|
||||
}
|
||||
};
|
||||
|
||||
let round_overview = move || {
|
||||
(0..30).map(|r| {
|
||||
let set_round = move |_| {
|
||||
set_round.set(r);
|
||||
};
|
||||
|
||||
let round = perround_resource.get().map(|rs| rs.get(r).cloned()).flatten();
|
||||
let reason = round.map(|r| r.reason);
|
||||
|
||||
// Upper is CT by default and lower is T by default
|
||||
let mut upper_symbol = match &reason {
|
||||
Some(common::demo_analysis::RoundWinReason::TKilled) => view! { <span>Killed Ts</span> }.into_view(),
|
||||
Some(common::demo_analysis::RoundWinReason::BombDefused) => view! { <span>Defused</span> }.into_view(),
|
||||
Some(common::demo_analysis::RoundWinReason::TimeRanOut) => view! { <span>Out of Time</span> }.into_view(),
|
||||
_ => view! {}.into_view(),
|
||||
};
|
||||
let mut lower_symbol = match &reason {
|
||||
Some(common::demo_analysis::RoundWinReason::CTKilled) => view! { <span>Killed CTs</span> }.into_view(),
|
||||
Some(common::demo_analysis::RoundWinReason::BombExploded) => view! { <span>Exploded</span> }.into_view(),
|
||||
_ => view! {}.into_view(),
|
||||
};
|
||||
|
||||
if (12..27).contains(&r) {
|
||||
core::mem::swap(&mut upper_symbol, &mut lower_symbol);
|
||||
}
|
||||
|
||||
view! {
|
||||
class=style,
|
||||
<div class="round_entry" style=format!("grid-column: {}; grid-row: 1", to_coloumn(r))> { upper_symbol } </div>
|
||||
<p on:click=set_round class="round_entry round_number" style=format!("grid-column: {}; grid-row: 2", to_coloumn(r))>{ r + 1 }</p>
|
||||
<div class="round_entry" style=format!("grid-column: {}; grid-row: 3", to_coloumn(r))> { lower_symbol } </div>
|
||||
}
|
||||
}).collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
view! {
|
||||
class=style,
|
||||
<h3>Per Round</h3>
|
||||
|
||||
<div class="round_overview">
|
||||
{ round_overview }
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3> Round { move || round.get() + 1 } </h3>
|
||||
<div>
|
||||
<ul> { events_list } </ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
76
frontend/src/demo/scoreboard.rs
Normal file
76
frontend/src/demo/scoreboard.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use leptos::*;
|
||||
|
||||
#[leptos::component]
|
||||
pub fn scoreboard() -> impl leptos::IntoView {
|
||||
use leptos::Suspense;
|
||||
|
||||
let scoreboard_resource =
|
||||
create_resource(leptos_router::use_params_map(), |params| async move {
|
||||
let id = params.get("id").unwrap();
|
||||
|
||||
let res =
|
||||
reqwasm::http::Request::get(&format!("/api/demos/{}/analysis/scoreboard", id))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
res.json::<common::demo_analysis::ScoreBoard>()
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
view! {
|
||||
<h2>Scoreboard</h2>
|
||||
|
||||
<Suspense
|
||||
fallback=move || view! { <p>Loading Scoreboard data</p> }
|
||||
>
|
||||
<TeamScoreboard info=scoreboard_resource team_name="Team 1".to_string() part=|s| s.team1 />
|
||||
<TeamScoreboard info=scoreboard_resource team_name="Team 2".to_string() part=|s| s.team2 />
|
||||
</Suspense>
|
||||
}
|
||||
}
|
||||
|
||||
#[leptos::component]
|
||||
fn team_scoreboard(info: Resource<leptos_router::ParamsMap, common::demo_analysis::ScoreBoard>, team_name: String, part: fn(common::demo_analysis::ScoreBoard) -> Vec<common::demo_analysis::ScoreBoardPlayer>) -> impl IntoView {
|
||||
let style = stylers::style! {
|
||||
"Team-Scoreboard",
|
||||
tr:nth-child(even) {
|
||||
background-color: #dddddd;
|
||||
}
|
||||
|
||||
th {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
th:nth-child(1) {
|
||||
width: 200px;
|
||||
}
|
||||
};
|
||||
|
||||
view! {
|
||||
class = style,
|
||||
<div>
|
||||
<h3>{ team_name }</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Kills</th>
|
||||
<th>Assists</th>
|
||||
<th>Deaths</th>
|
||||
<th>Damage</th>
|
||||
</tr>
|
||||
{
|
||||
move || {
|
||||
let value = info.get().map(|v| part(v));
|
||||
(value).into_iter().flat_map(|v| v).map(|s| {
|
||||
view! {
|
||||
class=style,
|
||||
<tr><td>{ s.name }</td><td>{ s.kills }</td><td>{ s.assists }</td><td>{ s.deaths }</td><td>{ s.damage }</td></tr>
|
||||
}
|
||||
}).collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,6 @@ use leptos_router::*;
|
||||
|
||||
use frontend::{Demo, Homepage, TopBar, UploadDemo};
|
||||
|
||||
async fn load_demos() -> Vec<common::BaseDemoInfo> {
|
||||
let res = reqwasm::http::Request::get("/api/demos/list")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let demos: Vec<common::BaseDemoInfo> = res.json().await.unwrap();
|
||||
|
||||
demos
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (upload_demo_read, upload_demo_write) = create_signal(frontend::DemoUploadStatus::Hidden);
|
||||
|
||||
@@ -28,10 +18,10 @@ fn main() {
|
||||
<Routes>
|
||||
<Route path="/" view=Homepage />
|
||||
<Route path="/demo/:id" view=Demo>
|
||||
<Route path="perround" view=frontend::demo::PerRound />
|
||||
<Route path="scoreboard" view=frontend::demo::Scoreboard />
|
||||
<Route path="perround" view=frontend::demo::perround::PerRound />
|
||||
<Route path="scoreboard" view=frontend::demo::scoreboard::Scoreboard />
|
||||
<Route path="heatmaps" view=frontend::demo::heatmap::Heatmaps />
|
||||
<Route path="" view=frontend::demo::Scoreboard />
|
||||
<Route path="" view=frontend::demo::scoreboard::Scoreboard />
|
||||
</Route>
|
||||
</Routes>
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user