Files
csgowtf/src/views/MatchView.vue
2022-03-25 15:49:54 +01:00

761 lines
19 KiB
Vue

<template>
<div :style="{ minHeight: pHeight + 'px' }" class="overlay">
<div class="match-wrapper">
<div class="head row m-auto text-center">
<div class="map-score">
<div class="score-team-1">
<h1
:class="
data.matchDetails.match_result === 1
? 'text-success'
: data.matchDetails.match_result === 0
? 'text-warning'
: 'text-danger'
"
>
{{ data.score[0] }}
</h1>
<div class="team-1">
<img alt="CT logo" src="/images/icons/ct_logo.svg" />
<img alt="T logo" src="/images/icons/t_logo.svg" />
</div>
<div class="team-avg-rank">
<img
v-if="data.matchDetails.parsed"
:alt="DisplayRank(Math.floor(data.team1Avg || 0))[1]"
:src="DisplayRank(Math.floor(data.team1Avg || 0))[0]"
:title="
'Average Team-Rank: ' +
DisplayRank(Math.floor(data.team1Avg || 0))[1]
"
class="team-avg-rank-icon helpicon"
/>
</div>
</div>
<div class="m-auto map">
<img
v-if="data.matchDetails.map"
:alt="data.matchDetails.map"
:src="
'/images/map_icons/map_icon_' + data.matchDetails.map + '.svg'
"
:title="FixMapName(data.matchDetails.map)"
class="map-icon"
/>
<img
v-if="!data.matchDetails.map"
:src="'/images/map_icons/map_icon_lobby_mapveto.svg'"
alt="Map icon"
class="map-icon"
title="Map unknown"
/>
</div>
<div class="score-team-2">
<h1
:class="
data.matchDetails.match_result === 2
? 'text-success'
: data.matchDetails.match_result === 0
? 'text-warning'
: 'text-danger'
"
>
{{ data.score[1] }}
</h1>
<div class="team-2">
<img alt="T logo" src="/images/icons/t_logo.svg" />
<img alt="CT logo" src="/images/icons/ct_logo.svg" />
</div>
<div class="team-avg-rank">
<img
v-if="data.matchDetails.parsed"
:alt="DisplayRank(Math.floor(data.team2Avg || 0))[1]"
:src="DisplayRank(Math.floor(data.team2Avg || 0))[0]"
:title="
'Average Team-Rank: ' +
DisplayRank(Math.floor(data.team2Avg || 0))[1]
"
class="team-avg-rank-icon helpicon"
/>
</div>
</div>
</div>
<div class="text">
<p class="text-center text-muted fs-6 mb-1">
Match lasted for
<span class="text-white">{{
FormatDuration(data.matchDetails.duration)
}}</span>
</p>
<p class="text-center text-muted fs-6">
on
<span class="text-white">{{
FormatFullDate(data.matchDetails.date)
}}</span>
</p>
<div class="text-center fs-6">
<img
v-if="data.matchDetails.max_rounds === 16"
alt="Match length"
class="match-len helpicon"
src="/images/icons/timer_short.svg"
title="Short Match"
/>
<img
v-if="
data.matchDetails.max_rounds === 30 ||
!data.matchDetails.max_rounds
"
alt="Match length"
class="match-len helpicon"
src="/images/icons/timer_long.svg"
title="Long Match"
/>
<span v-if="data.matchDetails.parsed" class="text-muted px-2"
>—</span
>
<img
v-if="data.matchDetails.parsed"
:alt="DisplayRank(Math.floor(data.matchDetails.avg_rank || 0))[1]"
:src="DisplayRank(Math.floor(data.matchDetails.avg_rank || 0))[0]"
:title="
'Average Rank: ' +
DisplayRank(Math.floor(data.matchDetails.avg_rank || 0))[1]
"
class="rank-icon helpicon"
/>
<span
v-if="data.matchDetails.parsed && data.matchDetails.replay_url"
class="text-muted px-2"
>—</span
>
<div
v-if="data.matchDetails.parsed && data.matchDetails.replay_url"
class="btn-group"
>
<i
id="downloadMenuBtn"
aria-hidden="true"
class="fa fa-ellipsis-h mx-2"
title="Click for more"
@click.prevent="handleDownloadMenu"
></i>
<div id="downloadGroup" class="group">
<a
v-if="data.matchDetails.replay_url"
:href="data.matchDetails.replay_url"
target="_blank"
title="Download Demo"
>
<i
id="downloadDemo"
aria-hidden="true"
class="fa fa-download mx-2"
></i>
</a>
<a
v-if="data.matchDetails.share_code"
:href="
'steam://rungame/730/76561202255233023/+csgo_download_match ' +
data.matchDetails.share_code
"
target="_blank"
title="Watch Demo"
>
<i
id="replayDemo"
aria-hidden="true"
class="fa fa-television mx-2"
></i>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="nav navbar-dark navbar-expand-lg">
<button
aria-controls="matchNav"
aria-expanded="false"
aria-label="Toggle navigation"
class="navbar-toggler"
data-bs-target="#matchNav"
data-bs-toggle="collapse"
type="button"
>
<span class="navbar-toggler-icon"></span>
</button>
<div
id="matchNav"
class="collapse navbar-collapse justify-content-between"
>
<ul class="list-unstyled d-flex m-auto">
<li
:title="
!data.matchDetails.parsed ? 'This demo has not been parsed' : ''
"
class="list-item nav-item"
>
<router-link
:to="'/match/' + data.matchDetails.match_id"
class="nav-link"
replace
>Scoreboard
</router-link>
</li>
<li
:title="
!data.matchDetails.parsed ? 'This demo has not been parsed' : ''
"
class="list-item nav-item"
>
<router-link
:class="!data.matchDetails.parsed ? 'disabled' : ''"
:disabled="!data.matchDetails.parsed"
:to="'/match/' + data.matchDetails.match_id + '/economy'"
class="nav-link"
replace
>Economy
</router-link>
</li>
<li
:title="
!data.matchDetails.parsed ? 'This demo has not been parsed' : ''
"
class="list-item nav-item"
>
<router-link
:class="!data.matchDetails.parsed ? 'disabled' : ''"
:disabled="!data.matchDetails.parsed"
:to="'/match/' + data.matchDetails.match_id + '/details'"
class="nav-link"
replace
>Details
</router-link>
</li>
<li
:title="
!data.matchDetails.parsed ? 'This demo has not been parsed' : ''
"
class="list-item nav-item"
>
<router-link
:class="!data.matchDetails.parsed ? 'disabled' : ''"
:disabled="!data.matchDetails.parsed"
:to="'/match/' + data.matchDetails.match_id + '/flashes'"
class="nav-link"
replace
>Flashes
</router-link>
</li>
<li
:title="
!data.matchDetails.parsed ? 'This demo has not been parsed' : ''
"
class="list-item nav-item"
>
<router-link
:class="!data.matchDetails.parsed ? 'disabled' : ''"
:disabled="!data.matchDetails.parsed"
:to="'/match/' + data.matchDetails.match_id + '/damage'"
class="nav-link"
replace
>Damage
</router-link>
</li>
<li
:title="
!data.matchDetails.parsed ? 'This demo has not been parsed' : ''
"
class="list-item nav-item"
>
<router-link
:class="!data.matchDetails.parsed ? 'disabled' : ''"
:disabled="!data.matchDetails.parsed"
:to="'/match/' + data.matchDetails.match_id + '/chat'"
class="nav-link"
replace
>Chat
</router-link>
</li>
</ul>
</div>
</div>
<div id="scoreWrapper" class="scoreboard">
<router-view v-if="data.score.length === 2 && data.stats" name="score" />
</div>
</div>
</template>
<script setup lang="ts">
import {
onBeforeMount,
onBeforeUnmount,
onMounted,
reactive,
ref,
watch,
} from "vue";
import {
closeNav,
DisplayRank,
errorHandling,
FixMapName,
FormatDuration,
FormatFullDate,
GetMatchDetails,
GoToLink,
LoadImage,
ProcessName,
setAppDivBackground,
setBgImgDisplay,
} from "@/utils";
import { useRoute } from "vue-router";
import { DateTime } from "luxon";
import { FOOTER_HEIGHT, NAV_HEIGHT } from "@/constants";
import { useInfoStateStore } from "@/stores/infoState";
import { useMatchDetailsStore } from "@/stores/matchDetails";
import type { Match, MatchStats } from "@/types";
import { usePlayersArrStore } from "@/stores/playersArr";
const route = useRoute();
const pHeight = ref(0);
const matchIdPattern = /^\d{19}$/;
const infoStateStore = useInfoStateStore();
const matchDetailsStore = useMatchDetailsStore();
const playersArrStore = usePlayersArrStore();
// Props
const props = defineProps<{
match_id: string;
}>();
// Refs
interface dataI {
player_id: string;
matchDetails?: Match;
stats?: MatchStats[];
score: number[];
team1Avg: number;
team2Avg: number;
}
const data: dataI = reactive({
player_id: "",
score: [0],
team1Avg: 0,
team2Avg: 0,
});
const getWindowHeight = () => {
const navHeight = document.getElementsByTagName("nav")[0].clientHeight;
const footerHeight = document.getElementsByTagName("footer")[0].clientHeight;
// 70 = nav-height | 108.5 = footer-height
return window.innerHeight - navHeight - footerHeight;
};
pHeight.value = getWindowHeight();
// Functions
const GetMatch = async () => {
if (matchIdPattern.test(props.match_id)) {
const [res, info] = await GetMatchDetails(props.match_id);
if (info.message !== "") infoStateStore.addInfo(info);
if (res !== null) {
if (res.map)
document.title = `${FixMapName(res.map)} ► ${res.score[0]} : ${
res.score[1]
} ◄ ${DateTime.fromSeconds(res.date).toLocaleString(
DateTime.DATETIME_SHORT
)} | csgoWTF`;
else document.title = `Match-Details | csgoWTF`;
matchDetailsStore.matchDetails = res;
checkRoute();
data.matchDetails = matchDetailsStore.matchDetails;
data.matchDetails.stats?.forEach((p) => {
p.player!.name = ProcessName(p.player?.name || "");
});
data.stats = data.matchDetails.stats;
data.score = data.matchDetails.score;
// Set avg team ranks
let pCount = 1;
data.team1Avg =
Math.floor(
getTeamAvgRank(1).reduce((a, b) => {
if (a !== 0 && b !== 0) pCount++;
return (a ? a : 0) + (b ? b : 0);
}) || 0
) / pCount;
pCount = 1;
data.team2Avg =
Math.floor(
getTeamAvgRank(2).reduce((a, b) => {
if (a !== 0 && b !== 0) pCount++;
return (a ? a : 0) + (b ? b : 0);
}) || 0
) / pCount;
LoadImage(data.matchDetails.map ? data.matchDetails.map : "random");
playersArrStore.playersArr = data.stats;
} else {
setBgImgDisplay("none", HTMLImageElement);
}
} else {
errorHandling(404);
}
};
const checkRoute = () => {
if (route.fullPath.split("/")[3]) {
const sub = route.fullPath.split("/")[3];
if (matchIdPattern.test(props.match_id)) {
GoToLink(`/match/${props.match_id}/${sub}`);
} else {
errorHandling(404);
}
} else {
if (matchIdPattern.test(props.match_id))
GoToLink(`/match/${props.match_id}`);
else {
errorHandling(404);
}
}
};
const getTeamAvgRank = (team: number) => {
let arr = [];
for (let i = (team - 1) * 5; i < team * 5; i++) {
const player = data.stats !== undefined ? data.stats[i] : undefined;
if (player !== undefined)
arr.push(player?.rank?.old ? player?.rank?.old : 0);
}
return arr;
};
const handleDownloadMenu = () => {
const downloadGroup = document.getElementById(
"downloadGroup"
) as HTMLDivElement;
const menuBtn = document.getElementById("downloadMenuBtn") as HTMLElement;
let opacity = window.getComputedStyle(menuBtn).getPropertyValue("opacity");
let tmpOpacity = parseFloat(opacity);
function show() {
if (tmpOpacity < 1) {
tmpOpacity = tmpOpacity + 0.1;
downloadGroup.style.opacity = tmpOpacity.toString();
} else {
clearInterval(0);
}
}
function hide() {
if (tmpOpacity > 0) {
tmpOpacity = tmpOpacity - 0.1;
menuBtn.style.opacity = opacity;
} else {
menuBtn.style.display = "none";
tmpOpacity = 0;
downloadGroup.style.opacity = tmpOpacity.toString();
downloadGroup.style.display = "block";
setInterval(show, 35);
}
}
setInterval(hide, 35);
};
// Watchers
watch(() => props.match_id, GetMatch);
// Run on create
onBeforeMount(() => {
GetMatch();
});
onBeforeUnmount(() => {
matchDetailsStore.$reset();
});
onMounted(() => {
const headHeight = 230;
const navHeight = 42;
const height =
window.innerHeight - NAV_HEIGHT - FOOTER_HEIGHT - headHeight - navHeight;
const scoreWrapper = document.getElementById(
"scoreWrapper"
) as HTMLDivElement;
scoreWrapper.style.minHeight = height + "px";
setAppDivBackground(
"linear-gradient(90deg, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.85) 30%, rgba(0, 0, 0, 0.85) 70%, rgba(0, 0, 0, .6) 100%)",
HTMLDivElement
);
setBgImgDisplay("initial", HTMLImageElement);
});
window.onresize = () => {
pHeight.value = getWindowHeight();
};
document.addEventListener("click", () => {
closeNav("matchNav");
});
</script>
<style lang="scss" scoped>
.head {
height: 230px;
background: linear-gradient(
90deg,
rgba(0, 0, 0, 0.3) 0%,
rgba(0, 0, 0, 0.55) 30%,
rgba(0, 0, 0, 0.55) 70%,
rgba(0, 0, 0, 0.3) 100%
);
.map-score {
display: flex;
position: relative;
.map img {
width: auto;
height: 100px;
margin: 10px 0;
}
.score-team-1,
.score-team-2 {
position: absolute;
top: 2rem;
h1 {
margin: 0 auto 0.5rem;
font-size: 4rem;
}
.team-avg-rank {
margin: 3.5rem auto 0;
.team-avg-rank-icon {
width: 60px;
}
}
.team-1,
.team-2 {
position: relative;
color: white;
font-size: 1rem;
opacity: 0.8;
img {
position: absolute;
width: 30px;
height: 30px;
&:first-child {
z-index: 1;
}
&:last-child {
margin-left: 20px;
z-index: 0 !important;
}
}
}
.team-1 {
right: 1.4rem;
}
.team-2 {
left: -1.5rem;
}
}
.score-team-1 {
left: 25%;
}
.score-team-2 {
right: 25%;
}
}
.text {
.rank-icon {
width: 60px;
}
.match-len {
width: 22px;
height: 22px;
}
#downloadMenuBtn {
cursor: pointer;
font-size: 1.3rem;
margin-left: -5px;
}
.group {
display: none;
margin-left: -5px;
i {
cursor: pointer;
color: white;
font-size: 1.3rem;
&:hover,
&:focus {
color: var(--bs-warning);
}
}
}
}
}
.nav {
max-width: 100vw;
min-height: 42px;
background: rgba(0, 0, 0, 0.9);
background: linear-gradient(
90deg,
rgba(0, 0, 0, 0.7) 0%,
rgba(0, 0, 0, 0.95) 30%,
rgba(0, 0, 0, 0.95) 70%,
rgba(0, 0, 0, 0.7) 100%
);
border-top: 1px solid rgba(255, 255, 255, 0.2);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
.nav-link {
text-decoration: none;
color: white;
&:hover {
background: var(--bs-info);
cursor: pointer;
}
}
.router-link-exact-active {
background: var(--bs-info);
}
.disabled {
color: #585858;
&:hover {
background: lime;
cursor: default;
}
}
}
#scoreWrapper {
display: flex;
justify-content: center;
flex-wrap: wrap;
}
.overlay {
z-index: 2;
width: 100%;
max-width: 100vw;
}
@media (max-width: 991px) {
.score-team-1,
.score-team-2 {
top: 1rem !important;
h1 {
font-size: 2.8rem !important;
margin-bottom: 0 !important;
}
.team-avg-rank {
margin: 2rem auto 0 !important;
.team-avg-rank-icon {
width: 50px !important;
}
}
.team-1,
.team-2 {
img {
width: 25px !important;
height: 25px !important;
}
}
.team-2 {
left: -1.3rem !important;
}
}
.score-team-1 {
left: 10% !important;
}
.score-team-2 {
right: 10% !important;
}
.nav {
button {
outline: 1px solid var(--bs-primary);
margin-left: auto;
float: right;
margin-right: 1rem;
&:focus {
box-shadow: none;
outline: 1px solid var(--bs-primary);
}
}
.navbar-collapse {
border-radius: 5px;
border: 1px solid var(--bs-primary);
ul {
flex-direction: column;
li {
width: 100%;
text-align: center;
}
}
}
}
#scoreWrapper {
justify-content: flex-start;
overflow-x: scroll;
overflow-y: hidden;
}
}
</style>