mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 00:01:56 +00:00
196 lines
6.4 KiB
TypeScript
196 lines
6.4 KiB
TypeScript
import { LitElement, html } from "lit";
|
|
import { customElement, property, state } from "lit/decorators.js";
|
|
import { PlayerStatsLeaf, PlayerStatsTree } from "../../../../core/ApiSchemas";
|
|
import { Difficulty, GameMode, GameType } from "../../../../core/game/Game";
|
|
import { PlayerStats } from "../../../../core/StatsSchemas";
|
|
import { renderNumber, translateText } from "../../../Utils";
|
|
import "./PlayerStatsGrid";
|
|
import "./PlayerStatsTable";
|
|
|
|
@customElement("player-stats-tree-view")
|
|
export class PlayerStatsTreeView extends LitElement {
|
|
@property({ type: Object }) statsTree?: PlayerStatsTree;
|
|
@state() selectedType: GameType = GameType.Public;
|
|
@state() selectedMode: GameMode = GameMode.FFA;
|
|
@state() selectedDifficulty: Difficulty = Difficulty.Medium;
|
|
|
|
private get availableTypes(): GameType[] {
|
|
if (!this.statsTree) return [];
|
|
return Object.keys(this.statsTree) as GameType[];
|
|
}
|
|
|
|
private get availableModes(): GameMode[] {
|
|
const typeNode = this.statsTree?.[this.selectedType];
|
|
if (!typeNode) return [];
|
|
return Object.keys(typeNode) as GameMode[];
|
|
}
|
|
|
|
private get availableDifficulties(): Difficulty[] {
|
|
const typeNode = this.statsTree?.[this.selectedType];
|
|
const modeNode = typeNode?.[this.selectedMode];
|
|
if (!modeNode) return [];
|
|
return Object.keys(modeNode) as Difficulty[];
|
|
}
|
|
|
|
private labelForMode(m: GameMode) {
|
|
return m === GameMode.FFA
|
|
? translateText("player_modal.mode_ffa")
|
|
: translateText("player_modal.mode_team");
|
|
}
|
|
|
|
createRenderRoot() {
|
|
return this;
|
|
}
|
|
|
|
private getSelectedLeaf(): PlayerStatsLeaf | null {
|
|
const typeNode = this.statsTree?.[this.selectedType];
|
|
if (!typeNode) return null;
|
|
const modeNode = typeNode[this.selectedMode];
|
|
if (!modeNode) return null;
|
|
const diffNode = modeNode[this.selectedDifficulty];
|
|
if (!diffNode) return null;
|
|
return diffNode;
|
|
}
|
|
|
|
private getDisplayedStats(): PlayerStats | null {
|
|
const leaf = this.getSelectedLeaf();
|
|
if (!leaf || !leaf.stats) return null;
|
|
return leaf.stats;
|
|
}
|
|
|
|
private setGameType(t: GameType) {
|
|
if (this.selectedType === t) return;
|
|
this.selectedType = t;
|
|
const modes = this.availableModes;
|
|
if (!modes.includes(this.selectedMode)) {
|
|
this.selectedMode = modes[0] ?? this.selectedMode;
|
|
}
|
|
const diffs = this.availableDifficulties;
|
|
if (!diffs.includes(this.selectedDifficulty)) {
|
|
this.selectedDifficulty = diffs[0] ?? this.selectedDifficulty;
|
|
}
|
|
this.requestUpdate();
|
|
}
|
|
|
|
private setMode(m: GameMode) {
|
|
if (this.selectedMode === m) return;
|
|
this.selectedMode = m;
|
|
const diffs = this.availableDifficulties;
|
|
if (!diffs.includes(this.selectedDifficulty)) {
|
|
this.selectedDifficulty = diffs[0] ?? this.selectedDifficulty;
|
|
}
|
|
this.requestUpdate();
|
|
}
|
|
|
|
private setDifficulty(d: Difficulty) {
|
|
if (this.selectedDifficulty === d) return;
|
|
this.selectedDifficulty = d;
|
|
this.requestUpdate();
|
|
}
|
|
|
|
render() {
|
|
const types = this.availableTypes;
|
|
if (types.length && !types.includes(this.selectedType)) {
|
|
this.selectedType = types[0];
|
|
}
|
|
const modes = this.availableModes;
|
|
if (modes.length && !modes.includes(this.selectedMode)) {
|
|
this.selectedMode = modes[0];
|
|
}
|
|
const diffs = this.availableDifficulties;
|
|
if (diffs.length && !diffs.includes(this.selectedDifficulty)) {
|
|
this.selectedDifficulty = diffs[0];
|
|
}
|
|
|
|
const leaf = this.getSelectedLeaf();
|
|
const wlr = leaf
|
|
? leaf.losses === 0n
|
|
? Number(leaf.wins)
|
|
: Number(leaf.wins) / Number(leaf.losses)
|
|
: 0;
|
|
|
|
return html`
|
|
<!-- Type selector -->
|
|
<div class="flex gap-2 mt-2 justify-center">
|
|
${types.map(
|
|
(t) => html`
|
|
<button
|
|
class="text-xs px-2 py-0.5 rounded border ${this.selectedType ===
|
|
t
|
|
? "border-white/60 text-white"
|
|
: "border-white/20 text-gray-300"}"
|
|
@click=${() => this.setGameType(t)}
|
|
>
|
|
${t === GameType.Public
|
|
? translateText("player_modal.public")
|
|
: t === GameType.Private
|
|
? translateText("player_modal.private")
|
|
: translateText("player_modal.singleplayer")}
|
|
</button>
|
|
`,
|
|
)}
|
|
</div>
|
|
<!-- Mode selector -->
|
|
${modes.length
|
|
? html`<div class="flex gap-2 mt-2 justify-center">
|
|
${modes.map(
|
|
(m) => html`
|
|
<button
|
|
class="text-xs px-2 py-0.5 rounded border ${this
|
|
.selectedMode === m
|
|
? "border-white/60 text-white"
|
|
: "border-white/20 text-gray-300"}"
|
|
@click=${() => this.setMode(m)}
|
|
title=${translateText("player_modal.mode")}
|
|
>
|
|
${this.labelForMode(m)}
|
|
</button>
|
|
`,
|
|
)}
|
|
</div>`
|
|
: html``}
|
|
<!-- Difficulty selector -->
|
|
${diffs.length
|
|
? html`<div class="flex gap-2 mt-2 justify-center">
|
|
${diffs.map(
|
|
(d) =>
|
|
html` <button
|
|
class="text-xs px-2 py-0.5 rounded border ${this
|
|
.selectedDifficulty === d
|
|
? "border-white/60 text-white"
|
|
: "border-white/20 text-gray-300"}"
|
|
@click=${() => this.setDifficulty(d)}
|
|
title=${translateText("player_modal.difficulty")}
|
|
>
|
|
${translateText(`difficulty.${d}`)}
|
|
</button>`,
|
|
)}
|
|
</div>`
|
|
: html``}
|
|
${leaf
|
|
? html`
|
|
<hr class="w-2/3 border-gray-600 my-2" />
|
|
<player-stats-grid
|
|
.titles=${[
|
|
translateText("player_modal.stats_wins"),
|
|
translateText("player_modal.stats_losses"),
|
|
translateText("player_modal.stats_wlr"),
|
|
translateText("player_modal.stats_games_played"),
|
|
]}
|
|
.values=${[
|
|
renderNumber(leaf.wins),
|
|
renderNumber(leaf.losses),
|
|
wlr.toFixed(2),
|
|
renderNumber(leaf.total),
|
|
]}
|
|
></player-stats-grid>
|
|
<hr class="w-2/3 border-gray-600 my-2" />
|
|
<player-stats-table
|
|
.stats=${this.getDisplayedStats()}
|
|
></player-stats-table>
|
|
`
|
|
: html``}
|
|
`;
|
|
}
|
|
}
|