761 lines
19 KiB
Vue
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>
|