updated frontend from typescript to javascript

This commit is contained in:
cnachtigall1991
2021-10-05 00:16:20 +02:00
parent 27edfd0cfe
commit 886f182ff2
41 changed files with 1183 additions and 28 deletions

View File

@@ -9,8 +9,10 @@
},
"dependencies": {
"@popperjs/core": "^2.10.2",
"axios": "^0.22.0",
"bootstrap": "^5.1.1",
"core-js": "^3.6.5",
"luxon": "^2.0.2",
"vue": "^3.0.0",
"vue-router": "^4.0.0-0",
"vuex": "^4.0.0-0"
@@ -25,6 +27,8 @@
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^7.0.0",
"sass-loader": "^12.1.0"
"sass": "^1.42.1",
"sass-loader": "^10.2.0",
"string-sanitizer": "^2.0.2"
}
}

98
pnpm-lock.yaml generated
View File

@@ -8,20 +8,26 @@ specifiers:
'@vue/cli-plugin-vuex': ~4.5.0
'@vue/cli-service': ~4.5.0
'@vue/compiler-sfc': ^3.0.0
axios: ^0.22.0
babel-eslint: ^10.1.0
bootstrap: ^5.1.1
core-js: ^3.6.5
eslint: ^6.7.2
eslint-plugin-vue: ^7.0.0
sass-loader: ^12.1.0
luxon: ^2.0.2
sass: ^1.42.1
sass-loader: ^10.2.0
string-sanitizer: ^2.0.2
vue: ^3.0.0
vue-router: ^4.0.0-0
vuex: ^4.0.0-0
dependencies:
'@popperjs/core': 2.10.2
axios: 0.22.0
bootstrap: 5.1.1_@popperjs+core@2.10.2
core-js: 3.18.1
luxon: 2.0.2
vue: 3.2.19
vue-router: 4.0.11_vue@3.2.19
vuex: 4.0.2_vue@3.2.19
@@ -31,12 +37,14 @@ devDependencies:
'@vue/cli-plugin-eslint': 4.5.13_a58cf9e4d577795b8c257bee96d49483
'@vue/cli-plugin-router': 4.5.13_@vue+cli-service@4.5.13
'@vue/cli-plugin-vuex': 4.5.13_@vue+cli-service@4.5.13
'@vue/cli-service': 4.5.13_0c7cfb9d6b60c37eed7038267d6bc444
'@vue/cli-service': 4.5.13_14d4d8446413226c330137c0feac1b91
'@vue/compiler-sfc': 3.2.19
babel-eslint: 10.1.0_eslint@6.8.0
eslint: 6.8.0
eslint-plugin-vue: 7.18.0_eslint@6.8.0
sass-loader: 12.1.0
sass: 1.42.1
sass-loader: 10.2.0_sass@1.42.1
string-sanitizer: 2.0.2
packages:
@@ -1565,7 +1573,7 @@ packages:
dependencies:
'@babel/core': 7.15.5
'@vue/babel-preset-app': 4.5.13_vue@3.2.19
'@vue/cli-service': 4.5.13_0c7cfb9d6b60c37eed7038267d6bc444
'@vue/cli-service': 4.5.13_14d4d8446413226c330137c0feac1b91
'@vue/cli-shared-utils': 4.5.13
babel-loader: 8.2.2_99877201e3f6dd5396b321f0a88244ea
cache-loader: 4.1.0_webpack@4.46.0
@@ -1584,7 +1592,7 @@ packages:
'@vue/cli-service': ^3.0.0 || ^4.0.0-0
eslint: '>= 1.6.0 < 7.0.0'
dependencies:
'@vue/cli-service': 4.5.13_0c7cfb9d6b60c37eed7038267d6bc444
'@vue/cli-service': 4.5.13_14d4d8446413226c330137c0feac1b91
'@vue/cli-shared-utils': 4.5.13
eslint: 6.8.0
eslint-loader: 2.2.1_eslint@6.8.0+webpack@4.46.0
@@ -1602,7 +1610,7 @@ packages:
peerDependencies:
'@vue/cli-service': ^3.0.0 || ^4.0.0-0
dependencies:
'@vue/cli-service': 4.5.13_0c7cfb9d6b60c37eed7038267d6bc444
'@vue/cli-service': 4.5.13_14d4d8446413226c330137c0feac1b91
'@vue/cli-shared-utils': 4.5.13
dev: true
@@ -1611,10 +1619,10 @@ packages:
peerDependencies:
'@vue/cli-service': ^3.0.0 || ^4.0.0-0
dependencies:
'@vue/cli-service': 4.5.13_0c7cfb9d6b60c37eed7038267d6bc444
'@vue/cli-service': 4.5.13_14d4d8446413226c330137c0feac1b91
dev: true
/@vue/cli-service/4.5.13_0c7cfb9d6b60c37eed7038267d6bc444:
/@vue/cli-service/4.5.13_14d4d8446413226c330137c0feac1b91:
resolution: {integrity: sha512-CKAZN4iokMMsaUyJRU22oUAz3oS/X9sVBSKAF2/shFBV5xh3jqAlKl8OXZYz4cXGFLA6djNuYrniuLAo7Ku97A==}
engines: {node: '>=8'}
hasBin: true
@@ -1687,7 +1695,7 @@ packages:
pnp-webpack-plugin: 1.7.0
portfinder: 1.0.28
postcss-loader: 3.0.0
sass-loader: 12.1.0
sass-loader: 10.2.0_sass@1.42.1
ssri: 8.0.1
terser-webpack-plugin: 1.4.5_webpack@4.46.0
thread-loader: 2.1.3_webpack@4.46.0
@@ -2245,6 +2253,14 @@ packages:
resolution: {integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==}
dev: true
/axios/0.22.0:
resolution: {integrity: sha512-Z0U3uhqQeg1oNcihswf4ZD57O3NrR1+ZXhxaROaWpDmsDTx7T2HNBV2ulBtie2hwJptu8UvgnJoK+BIqdzh/1w==}
dependencies:
follow-redirects: 1.14.4
transitivePeerDependencies:
- debug
dev: false
/babel-eslint/10.1.0_eslint@6.8.0:
resolution: {integrity: sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==}
engines: {node: '>=6'}
@@ -2378,7 +2394,6 @@ packages:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
dev: true
optional: true
/bindings/1.5.0:
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
@@ -2761,7 +2776,6 @@ packages:
optionalDependencies:
fsevents: 2.3.2
dev: true
optional: true
/chownr/1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
@@ -4280,7 +4294,7 @@ packages:
readable-stream: 2.3.7
dev: true
/follow-redirects/1.14.4_debug@4.3.2:
/follow-redirects/1.14.4:
resolution: {integrity: sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==}
engines: {node: '>=4.0'}
peerDependencies:
@@ -4288,9 +4302,6 @@ packages:
peerDependenciesMeta:
debug:
optional: true
dependencies:
debug: 4.3.2
dev: true
/for-in/1.0.2:
resolution: {integrity: sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=}
@@ -4807,7 +4818,7 @@ packages:
engines: {node: '>=8.0.0'}
dependencies:
eventemitter3: 4.0.7
follow-redirects: 1.14.4_debug@4.3.2
follow-redirects: 1.14.4
requires-port: 1.0.0
transitivePeerDependencies:
- debug
@@ -5042,7 +5053,6 @@ packages:
dependencies:
binary-extensions: 2.2.0
dev: true
optional: true
/is-boolean-object/1.1.2:
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
@@ -5535,7 +5545,6 @@ packages:
emojis-list: 3.0.0
json5: 2.2.0
dev: true
optional: true
/locate-path/3.0.0:
resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
@@ -5613,6 +5622,17 @@ packages:
yallist: 3.1.1
dev: true
/lru-cache/6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
dependencies:
yallist: 4.0.0
dev: true
/luxon/2.0.2:
resolution: {integrity: sha512-ZRioYLCgRHrtTORaZX1mx+jtxKtKuI5ZDvHNAmqpUzGqSrR+tL4FVLn/CUGMA3h0+AKD1MAxGI5GnCqR5txNqg==}
dev: false
/magic-string/0.25.7:
resolution: {integrity: sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==}
dependencies:
@@ -7078,7 +7098,6 @@ packages:
dependencies:
picomatch: 2.3.0
dev: true
optional: true
/regenerate-unicode-properties/9.0.0:
resolution: {integrity: sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==}
@@ -7334,14 +7353,14 @@ packages:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
dev: true
/sass-loader/12.1.0:
resolution: {integrity: sha512-FVJZ9kxVRYNZTIe2xhw93n3xJNYZADr+q69/s98l9nTCrWASo+DR2Ot0s5xTKQDDEosUkatsGeHxcH4QBp5bSg==}
engines: {node: '>= 12.13.0'}
/sass-loader/10.2.0_sass@1.42.1:
resolution: {integrity: sha512-kUceLzC1gIHz0zNJPpqRsJyisWatGYNFRmv2CKZK2/ngMJgLqxTbXwe/hJ85luyvZkgqU3VlJ33UVF2T/0g6mw==}
engines: {node: '>= 10.13.0'}
peerDependencies:
fibers: '>= 3.1.0'
node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0
sass: ^1.3.0
webpack: ^5.0.0
webpack: ^4.36.0 || ^5.0.0
peerDependenciesMeta:
fibers:
optional: true
@@ -7351,7 +7370,19 @@ packages:
optional: true
dependencies:
klona: 2.0.4
loader-utils: 2.0.0
neo-async: 2.6.2
sass: 1.42.1
schema-utils: 3.1.1
semver: 7.3.5
dev: true
/sass/1.42.1:
resolution: {integrity: sha512-/zvGoN8B7dspKc5mC6HlaygyCBRvnyzzgD5khiaCfglWztY99cYoiTUksVx11NlnemrcfH5CEaCpsUKoW0cQqg==}
engines: {node: '>=8.9.0'}
hasBin: true
dependencies:
chokidar: 3.5.2
dev: true
/sax/1.2.4:
@@ -7376,6 +7407,15 @@ packages:
ajv-keywords: 3.5.2_ajv@6.12.6
dev: true
/schema-utils/3.1.1:
resolution: {integrity: sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==}
engines: {node: '>= 10.13.0'}
dependencies:
'@types/json-schema': 7.0.9
ajv: 6.12.6
ajv-keywords: 3.5.2_ajv@6.12.6
dev: true
/select-hose/2.0.0:
resolution: {integrity: sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=}
dev: true
@@ -7401,6 +7441,14 @@ packages:
hasBin: true
dev: true
/semver/7.3.5:
resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==}
engines: {node: '>=10'}
hasBin: true
dependencies:
lru-cache: 6.0.0
dev: true
/send/0.17.1:
resolution: {integrity: sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==}
engines: {node: '>= 0.8.0'}
@@ -7792,6 +7840,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/string-sanitizer/2.0.2:
resolution: {integrity: sha512-zECtWmUawolaVbUOdDRdhAM4jN7wl1sB4indjTmHpUFavzFSeYEDSVF85dZPPyDKoMRTJbrz+Tp0SjPPCWxscA==}
dev: true
/string-width/2.1.1:
resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==}
engines: {node: '>=4'}

21
scss/custom.scss Normal file
View File

@@ -0,0 +1,21 @@
// Custom.scss
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');
// Default variable overrides
$font-family-base: 'Roboto';
//$primary: #292c34;
//$secondary: #23262d;
//$body-bg: #2e3139;
//$blue: #4a90e2;
$body-color: white;
$primary: #888f98;
$secondary: #10121a;
$body-bg: #1b2732;
$blue: #5f7892;
$warning: #c3a235;
$info: $blue;
$success: #609926;
// Bootstrap
@import "../node_modules/bootstrap/scss/bootstrap";

View File

@@ -1,3 +1,27 @@
<template>
<router-view/>
<header>
<Nav/>
</header>
<main>
<div class="spacer bg-secondary"></div>
<router-view/>
</main>
<footer>
<Footer />
</footer>
</template>
<script>
import Nav from "@/components/Nav";
import Footer from "@/components/Footer";
export default {
components: {Footer, Nav},
}
</script>
<style>
.spacer {
height: 70px;
}
</style>

31
src/components/Footer.vue Normal file
View File

@@ -0,0 +1,31 @@
<template>
<div class="bg-secondary text-center pt-5 pb-4">
<div class="icons pb-4">
<a class="text-white" target="_blank" href="https://git.harting.dev/CSGOWTF/csgowtf">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-github"
viewBox="0 0 16 16">
<path
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
</svg>
</a>
</div>
<div class="text">
<p>This site is an open source project, originally created and maintained by <span class="text-warning">anonfunc</span> and <span
class="text-warning">vikingowl</span>.</p>
<p class="text-muted">For feedback contact us at <a class="text-muted text-decoration-none"
href="mailto:#">EMAIL</a></p>
</div>
</div>
</template>
<script>
export default {
name: "Footer",
}
</script>
<style scoped>
p {
font-size: .85rem;
}
</style>

110
src/components/Nav.vue Normal file
View File

@@ -0,0 +1,110 @@
<template>
<nav class="navbar navbar-expand navbar-dark fixed-top">
<div class="container-fluid w-75">
<div class="navbar-nav">
<router-link class="navbar-brand text-warning fw-bold fs-3" to="/">CSGO<span class="text-up text-white fw-bold">WTF</span>
</router-link>
<router-link class="nav-link" to="/explore">Explore</router-link>
</div>
<form class="d-flex col-5 justify-content-end" @keydown.enter.prevent="parseSearch">
<label for="search">
<svg class="bi bi-search" fill="currentColor" height="24" viewBox="0 0 16 16" width="24"
xmlns="http://www.w3.org/2000/svg">
<path
d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
</svg>
</label>
<input id="search" v-model="data.searchInput" aria-label="Search"
class="form-control w-75 bg-transparent border-0"
placeholder="SteamID64, Profile Link or Custom URL"
type="search">
</form>
</div>
</nav>
</template>
<script>
import {reactive} from "vue";
import {useStore} from 'vuex'
import {sanitize} from 'string-sanitizer'
import router from "../router";
export default {
name: 'Nav',
setup() {
const store = useStore()
const data = reactive({
searchInput: ''
})
const parseSearch = () => {
const input = data.searchInput
const customUrlPattern = 'https://steamcommunity.com/id/'
const profileUrlPattern = 'https://steamcommunity.com/profiles/'
const id64Pattern = /^\d{17}$/
store.state.id64 = ''
store.state.vanityUrl = ''
if (id64Pattern.test(input)) {
store.state.id64 = input
} else if (input.match(customUrlPattern)) {
store.state.vanityUrl = sanitize(input.split('/')[4].split('?')[0])
} else if (input.match(profileUrlPattern)) {
const tmp = input.split('/')[4].split('?')[0]
if (id64Pattern.test(tmp)) {
store.state.id64 = tmp
}
} else {
store.state.vanityUrl = sanitize(input)
}
if (store.state.id64 !== '' || store.state.vanityUrl !== '') {
data.searchInput = ''
if (store.state.id64) {
router.push(`/player/${store.state.id64}`)
} else if (store.state.vanityUrl) {
router.push(`/player/${store.state.vanityUrl}`)
}
}
}
return {
data, parseSearch
}
}
}
</script>
<style lang="scss" scoped>
nav {
height: 70px;
width: 100vw;
background: rgba(16, 18, 26, 0.5);
.text-up {
font-size: 40%;
vertical-align: top;
}
label {
padding-top: 6px;
}
input[type="search"] {
min-width: 300px;
max-width: 300px;
&:focus {
box-shadow: 0 4px 2px -2px rgba(95, 120, 146, 0.59);
transition: .2s ease-in-out;
transform: scale(.975);
}
&::placeholder {
color: #aaa;
}
}
}
</style>

View File

@@ -0,0 +1,71 @@
<template>
<table>
<thead>
<tr>
<th class="player__avatar"></th>
<th class="player__name"></th>
<th class="player__rank"></th>
<th class="player__kills cursor__help" title="Kills">K</th>
<th class="player__assist cursor__help" title="Assists">A</th>
<th class="player__deaths cursor__help" title="Deaths">D</th>
<th class="player__diff cursor__help" title="Kill death difference">+/-</th>
<th class="player__kd cursor__help" title="Kill death ratio">K/D</th>
<th class="player__adr cursor__help" title="Average damage per round">ADR</th>
<th class="player__hs cursor__help" title="Percentage of kills with a headshot">HS%</th>
<th class="player__kast cursor__help" title="Percentage of rounds with a Kill, Assist, Survived or Death Traded">
KAST
</th>
<th class="player__rating cursor__help" title="Estimated HLTV Rating 1.0">Rating</th>
</tr>
</thead>
<tbody v-for="player in props.stats" :key="player.player.steamid64">
<tr v-if="player.team_id === props.team_id" class="player">
<ScoreTeamPlayer
:assists="player.assists"
:avatar="player.player.avatar"
:deaths="player.deaths"
:dmg="player.extended?.dmg?.enemy"
:hs="player.headshot"
:kast="player.extended?.kast"
:kdiff="player.kills - player.deaths"
:kills="player.kills"
:mk_duo="player.extended?.multi_kills?.duo"
:mk_pent="player.extended?.multi_kills?.pent"
:mk_quad="player.extended?.multi_kills?.quad"
:mk_triple="player.extended?.multi_kills?.triple"
:name="player.player.name"
:rounds_played="props.rounds_played"
:rank="player.extended?.rank?.old"
/>
</tr>
</tbody>
</table>
</template>
<script>
import ScoreTeamPlayer from '@/components/ScoreTeamPlayer.vue'
export default {
name: 'ScoreTeam',
components: {ScoreTeamPlayer},
props: {
stats: {
type: Object,
required: true
},
rounds_played: {
type: Number,
required: true,
default: 0
},
team_id: {
type: Number,
required: true,
default: 1
}
},
setup(props) {
return {props}
}
}
</script>

View File

@@ -0,0 +1,130 @@
<template>
<td>
<img :src="props.avatar" alt="Player avatar" class="player__avatar">
</td>
<td class="player__name">
{{ props.name }}
</td>
<td>
<img :src="props.rank ? require('@/images/ranks/' + props.rank + '.png') : require('@/images/ranks/0.png')"
alt="Player rank"
class="player__rank">
</td>
<td class="player__kills">
{{ props.kills }}
</td>
<td class="player__assist">
{{ props.assists }}
</td>
<td class="player__deaths">
{{ props.deaths }}
</td>
<td :class="props.kdiff >= 0 ? 'text-success' : 'text-danger'" class="player__diff">
{{ props.kdiff }}
</td>
<td class="player__kd">
{{ (props.kills > 0 && props.deaths > 0) ? (props.kills / props.deaths).toFixed(2) : (props.kills > 0 && props.deaths === 0) ? props.kills : 0.00 }}
</td>
<td class="player__adr">
{{ (props.dmg / props.rounds_played).toFixed(2) }}
</td>
<td class="player__hs">
{{ (props.hs > 0 && props.kills > 0) ? (props.hs * 100 / props.kills).toFixed(0) + "%" : "0%" }}
</td>
<td class="player__kast">
{{ props.kast ? props.kast + "%" : "-" }}
</td>
<td class="player__rating">
{{
GetHLTV_1(props.kills, props.rounds_played, props.deaths, props.mk_duo, props.mk_triple, props.mk_quad, props.mk_pent)
}}
</td>
</template>
<script>
import {GetHLTV_1} from "../utils";
export default {
name: 'ScoreTeamPlayer',
props: {
avatar: {
type: String,
required: true,
default: 'Avatar'
},
name: {
type: String,
required: true,
default: 'Name'
},
rank: {
type: Number,
required: true,
default: 0
},
kills: {
type: Number,
required: true,
default: 0
},
assists: {
type: Number,
required: true,
default: 0
},
deaths: {
type: Number,
required: true,
default: 0
},
kdiff: {
type: Number,
required: true,
default: 0
},
hs: {
type: Number,
required: true,
default: 0
},
rounds_played: {
type: Number,
required: true,
default: 0
},
mk_duo: {
type: Number,
required: true,
default: 0
},
mk_triple: {
type: Number,
required: true,
default: 0
},
mk_quad: {
type: Number,
required: true,
default: 0
},
mk_pent: {
type: Number,
required: true,
default: 0
},
kast: {
type: Number,
required: true,
default: 0
},
dmg: {
type: Number,
required: true,
default: 0
},
},
setup(props) {
return {props, GetHLTV_1}
}
}
</script>

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M7,12 L14.5,12 C16.277025,12 17.7447372,10.6756742 17.970024,8.96013518 C16.2885152,8.7047201 15,7.25283448 15,5.5 C15,3.56700338 16.5670034,2 18.5,2 C20.4329966,2 22,3.56700338 22,5.5 C22,7.27155475 20.6838151,8.73569805 18.9759671,8.96790818 C18.7419236,11.2333126 16.8272778,13 14.5,13 L7,13 L7,15.0354444 C8.69614707,15.2780593 10,16.736764 10,18.5 C10,20.4329966 8.43299662,22 6.5,22 C4.56700338,22 3,20.4329966 3,18.5 C3,16.736764 4.30385293,15.2780593 6,15.0354444 L6,8.96455557 C4.30385293,8.72194074 3,7.26323595 3,5.5 C3,3.56700338 4.56700338,2 6.5,2 C8.43299662,2 10,3.56700338 10,5.5 C10,7.26323595 8.69614707,8.72194074 7,8.96455557 L7,12 Z M4,18.5 C4,19.8807119 5.11928813,21 6.5,21 C7.88071187,21 9,19.8807119 9,18.5 C9,17.1192881 7.88071187,16 6.5,16 C5.11928813,16 4,17.1192881 4,18.5 Z M4,5.5 C4,6.88071187 5.11928813,8 6.5,8 C7.88071187,8 9,6.88071187 9,5.5 C9,4.11928813 7.88071187,3 6.5,3 C5.11928813,3 4,4.11928813 4,5.5 Z M18.5,3 C17.1192881,3 16,4.11928813 16,5.5 C16,6.88071187 17.1192881,8 18.5,8 C19.8807119,8 21,6.88071187 21,5.5 C21,4.11928813 19.8807119,3 18.5,3 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
src/images/ranks/0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
src/images/ranks/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
src/images/ranks/10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
src/images/ranks/11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
src/images/ranks/12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
src/images/ranks/13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
src/images/ranks/14.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
src/images/ranks/15.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/images/ranks/16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
src/images/ranks/17.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
src/images/ranks/18.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
src/images/ranks/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
src/images/ranks/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
src/images/ranks/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
src/images/ranks/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/images/ranks/6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
src/images/ranks/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
src/images/ranks/8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
src/images/ranks/9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -2,5 +2,7 @@ import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'bootstrap'
import '../scss/custom.scss'
createApp(App).use(store).use(router).mount('#app')

View File

@@ -1,12 +1,43 @@
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
function lazyLoad(view) {
return () => import(`@/views/${view}.vue`)
}
const routes = [
{
path: '/',
name: 'Home',
component: Home
component: lazyLoad('Home')
},
{
path: '/player/:id',
name: 'Player',
component: lazyLoad('Player'),
props: true
},
{
path: '/player/:id/matches',
name: 'MyMatches',
component: lazyLoad('MyMatches'),
props: true
},
{
path: '/match/:match_id',
name: 'Match',
component: lazyLoad('Match'),
props: true
},
{
path: '/explore',
name: 'Explore',
component: lazyLoad('Explore'),
props: true
},
{
path: '/:pathMatch(.*)*',
redirect: '/'
}
]
const router = createRouter({

View File

@@ -2,6 +2,8 @@ import { createStore } from 'vuex'
export default createStore({
state: {
id64: '',
vanityUrl: ''
},
mutations: {
},

50
src/utils/index.js Normal file
View File

@@ -0,0 +1,50 @@
import {DateTime} from "luxon/build/es6/luxon";
import router from '@/router'
export const FormatDuration = (d) => {
const hours = Math.floor(d / 3600)
const num = d % 3600
const minutes = Math.floor(num % 3600 / 60)
const seconds = Math.floor(num % 3600 % 60)
if (hours !== 0)
return `${hours}:${minutes < 10 ? '0' + minutes : minutes}:${seconds < 10 ? '0' + seconds : seconds}`
else
return `${minutes < 10 ? '0' + minutes : minutes}:${seconds < 10 ? '0' + seconds : seconds}`
}
export const FormatDate = (date) => {
const matchDate = DateTime.fromISO(date)
const diff = DateTime.now().diff(matchDate)
if (diff.as('days') > 10)
return matchDate.toLocaleString({weekday: 'short', day: 'numeric', month: 'numeric', year: 'numeric'})
else if (diff.as('days') < 1)
if (diff.as('hours') < 1)
return Math.floor(diff.as('minutes')) + ' minutes ago'
else
return Math.floor(diff.as('hours')) + ' hours ago'
else
return Math.floor(diff.as('days')) + ' days ago'
}
export const GoToMatch = (id) => {
router.push(`/match/${id}`)
}
export const GoToPlayer = (id) => {
router.push(`/player/${id}`)
}
export const GetHLTV_1 = (kills = 0, rounds, deaths = 0, k2 = 0, k3 = 0, k4 = 0, k5 = 0) => {
const k1 = kills - k2 - k3 - k4 - k5
const Weight_KPR = 0.679 // weight kills per round
const Weight_SPR = 0.317 // weight survived rounds per round
const Weight_RMK = 1.277 // weight value calculated from rounds with multiple kills (1k + 4*2k + 9*3k + 16*4k + 25*5k)
const KillRating = kills / rounds / Weight_KPR
const SurvivalRating = (rounds - deaths) / rounds / Weight_SPR
const RoundsWithMultipleKillsRating = (k1 + 4 * k2 + 9 * k3 + 16 * k4 + 25 * k5) / rounds / Weight_RMK
return ((KillRating + 0.7 * SurvivalRating + RoundsWithMultipleKillsRating) / 2.7).toFixed(2)
}

9
src/views/Explore.vue Normal file
View File

@@ -0,0 +1,9 @@
<template>
Explore
</template>
<script>
export default {
name: 'Explore'
}
</script>

View File

@@ -1,3 +1,112 @@
<template>
Home
<div class="main-content content text-center">
<div class="head">
<h1 class="text-warning fw-bold">CSGO<span class="text-up text-white">WTF</span></h1>
<h3>Open source CSGO data platform</h3>
</div>
<div class="body row m-auto mt-5 mb-5">
<div class="col-4">
<svg class="mb-4" height="80" viewBox="0 0 24 24" width="80" xmlns="http://www.w3.org/2000/svg">
<path d="M7,12 L14.5,12 C16.277025,12 17.7447372,10.6756742 17.970024,8.96013518 C16.2885152,8.7047201 15,7.25283448 15,5.5 C15,3.56700338 16.5670034,2 18.5,2 C20.4329966,2 22,3.56700338 22,5.5 C22,7.27155475 20.6838151,8.73569805 18.9759671,8.96790818 C18.7419236,11.2333126 16.8272778,13 14.5,13 L7,13 L7,15.0354444 C8.69614707,15.2780593 10,16.736764 10,18.5 C10,20.4329966 8.43299662,22 6.5,22 C4.56700338,22 3,20.4329966 3,18.5 C3,16.736764 4.30385293,15.2780593 6,15.0354444 L6,8.96455557 C4.30385293,8.72194074 3,7.26323595 3,5.5 C3,3.56700338 4.56700338,2 6.5,2 C8.43299662,2 10,3.56700338 10,5.5 C10,7.26323595 8.69614707,8.72194074 7,8.96455557 L7,12 Z M4,18.5 C4,19.8807119 5.11928813,21 6.5,21 C7.88071187,21 9,19.8807119 9,18.5 C9,17.1192881 7.88071187,16 6.5,16 C5.11928813,16 4,17.1192881 4,18.5 Z M4,5.5 C4,6.88071187 5.11928813,8 6.5,8 C7.88071187,8 9,6.88071187 9,5.5 C9,4.11928813 7.88071187,3 6.5,3 C5.11928813,3 4,4.11928813 4,5.5 Z M18.5,3 C17.1192881,3 16,4.11928813 16,5.5 C16,6.88071187 17.1192881,8 18.5,8 C19.8807119,8 21,6.88071187 21,5.5 C21,4.11928813 19.8807119,3 18.5,3 Z"
fill="white"/>
</svg>
<h4 class="fw-light">Open Source</h4>
<p class="w-75 m-auto fw-light">All project code is open source and available for contributors to improve and
modify.</p>
</div>
<div class="col-4">
<svg class="bi bi-bar-chart-fill mb-4" fill="currentColor" height="80" viewBox="0 0 16 16"
width="80" xmlns="http://www.w3.org/2000/svg">
<path
d="M1 11a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-3zm5-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7zm5-5a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V2z"/>
</svg>
<h4 class="fw-light">In-Depth Data</h4>
<p class="w-75 m-auto fw-light">Parsing replay files provides highly detailed match data.</p>
</div>
<div class="col-4">
<svg class="bi bi-stars mb-4" fill="currentColor" height="80" viewBox="0 0 16 16" width="80"
xmlns="http://www.w3.org/2000/svg">
<path
d="M7.657 6.247c.11-.33.576-.33.686 0l.645 1.937a2.89 2.89 0 0 0 1.829 1.828l1.936.645c.33.11.33.576 0 .686l-1.937.645a2.89 2.89 0 0 0-1.828 1.829l-.645 1.936a.361.361 0 0 1-.686 0l-.645-1.937a2.89 2.89 0 0 0-1.828-1.828l-1.937-.645a.361.361 0 0 1 0-.686l1.937-.645a2.89 2.89 0 0 0 1.828-1.828l.645-1.937zM3.794 1.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387A1.734 1.734 0 0 0 4.593 5.69l-.387 1.162a.217.217 0 0 1-.412 0L3.407 5.69A1.734 1.734 0 0 0 2.31 4.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387A1.734 1.734 0 0 0 3.407 2.31l.387-1.162zM10.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732L9.1 2.137a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L10.863.1z"/>
</svg>
<h4 class="fw-light">Free of Charge</h4>
<p class="w-75 m-auto fw-light">This service is free of charge. If you want to support us, just contact us.</p>
</div>
</div>
<hr class="m-auto text-muted">
<div class="foot">
MORE TEXT
</div>
<span class="image-by">Image by <a class="text-decoration-none text-info" href="https://unsplash.com/photos/6ou8gWpS9ns"
target="_blank">Fredrick Tendong</a></span>
</div>
</template>
<script>
import {onMounted} from "vue";
export default {
name: 'Home',
setup() {
onMounted(() => {
document.title = 'Home | CSGO-W.TV'
})
}
}
</script>
<style scoped lang="scss">
.main-content {
.head {
background-image: url("https://images.unsplash.com/photo-1560419015-7c427e8ae5ba?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1470&q=80");
background-repeat: no-repeat;
background-size: cover;
background-position: center;
padding-top: 10rem;
padding-bottom: 10rem;
h1 {
font-size: 5rem;
&:before {
content: 'CSGO';
position: absolute;
text-shadow: 0 0 1rem rgba(0, 0, 0, 0.5);
}
}
.text-up {
font-size: 40%;
vertical-align: top;
text-shadow: 10px -5px 1rem rgba(0, 0, 0, 0.5);
}
h3 {
font-size: 2.5rem;
font-weight: lighter;
}
}
.body, hr {
width: 65%;
p {
font-size: .9rem;
}
}
.foot {
margin-top: 5rem;
padding-bottom: 5rem;
}
.image-by {
position: absolute;
bottom: -35px;
right: 2rem;
font-size: .8rem;
}
}
</style>

177
src/views/Match.vue Normal file
View File

@@ -0,0 +1,177 @@
<template>
<div class="pt-3">
<p class="text-center fs-6">Average Rank: <img :src="require('@/images/ranks/' + data.avgRank + '.png')"
alt="Rank icon"
class="rank-icon mx-2"/></p>
<div v-if="data.matchDetails" class="head row m-auto mb-3 text-center">
<div class="m-auto">
<img :alt="data.matchDetails.map"
:src="mapImg.includes(data.matchDetails.map) ? require('@/images/maps/' + data.matchDetails.map + '.png') : require('@/images/maps/not_found.png')"
:title="data.matchDetails.map" class="map-icon">
</div>
</div>
</div>
<div class="nav">
<ul class="list-unstyled d-flex m-auto">
<li class="list-item active">Scoreboard</li>
<li class="list-item">Rounds</li>
<li class="list-item">Weapons</li>
<li class="list-item">Duels</li>
<li class="list-item">Heatmaps</li>
</ul>
</div>
<div class="container row m-auto">
<div class="score col-3 flex-column">
{{data.score[0]}}
-
{{data.score[1]}}
</div>
<div v-if="data.score.length === 2 && data.stats" class="col-9 d-flex flex-column">
<ScoreTeam :rounds_played="data.score.reduce((a, b) => a + b)" :stats="data.stats" :team_id="1"/>
<ScoreTeam :rounds_played="data.score.reduce((a, b) => a + b)" :stats="data.stats" :team_id="2"/>
</div>
</div>
</template>
<script>
import {defineAsyncComponent, onBeforeMount, reactive, watch} from "vue";
import axios from 'axios'
import {GetHLTV_1, GoToPlayer} from "../utils";
import "../components/ScoreTeam"
const ScoreTeam = defineAsyncComponent(() => import('../components/ScoreTeam'))
export default {
name: 'Match',
props: ['match_id'],
components: {ScoreTeam},
setup(props) {
// Vars
const mapImg = ['de_inferno', 'de_mirage', 'de_overpass']
// Refs
const data = reactive({
playerName: '',
matchDetails: {},
stats: [],
score: [0],
avgRank: 0,
})
// Functions
const GetMatch = () => {
axios
.get(`http://localhost:1337/http://localhost:8000/match/${props.match_id}`)
.then((response) => {
document.title = `${response.data.map} | CSGO-W.TF`
data.matchDetails = response.data
data.stats = response.data.stats
data.score = response.data.score
})
.catch((e) => {
console.log(e)
})
}
const GetAvgRank = () => {
let count = 0
let fullRank = 0
data.stats?.map(player => {
if (player.extended?.rank?.old) {
fullRank += player.extended?.rank?.old
count += 1
}
})
if (count === 0)
data.avgRank = 0
else
data.avgRank = Math.floor(fullRank / count)
}
// Watchers
watch(() => props.match_id, GetMatch)
watch(() => data.stats, GetAvgRank)
// Run on create
onBeforeMount(() => {
GetMatch()
})
return {
GetMatch, GetAvgRank, data, mapImg, GoToPlayer, GetHLTV_1
}
}
}
</script>
<style lang="scss">
.rank-icon {
max-width: 50px;
}
.map-icon {
max-width: 60px;
}
.score {
font-size: 3.5rem;
}
table {
width: 900px;
text-align: center;
tr {
height: 50px;
}
td {
padding: 5px 10px;
}
.cursor__help {
cursor: help;
}
.player__avatar {
width: 30px;
height: 30px;
border-radius: 50%;
}
.player__name {
text-align: left;
width: 150px;
}
.player__rank {
width: 60px;
}
.player__rating {
border-radius: 25% 25%;
}
}
.nav {
width: 100vw;
background: rgba(0, 0, 0, 0.6);
.list-item {
padding: 10px 10px;
&:hover {
background: var(--bs-info);
cursor: pointer;
}
}
.active {
background: var(--bs-info)
}
}
</style>

12
src/views/MyMatches.vue Normal file
View File

@@ -0,0 +1,12 @@
<template>
<table class="matches-table">
MyMatches
</table>
</template>
<script>
export default{
name: "MyMatches",
}
</script>

317
src/views/Player.vue Normal file
View File

@@ -0,0 +1,317 @@
<template>
<div class="wrapper">
<div class="container">
<div class="card mb-3 bg-transparent border-0" style="max-width: 540px;">
<div class="row g-0">
<div class="img-container col-md-4 pt-3">
<img
:src="data.playerDetails.avatar" alt="Player avatar" class="img-fluid avatar">
</div>
<div class="col-md-8">
<div class="card-body">
<h3 class="card-title"><a
:href="/^\d{17}$/.test(props.id) ? 'https://steamcommunity.com/profiles/' + props.id : 'https://steamcommunity.com/id/' + props.id"
class="text-decoration-none text-white"
target="_blank"
title="Open steam profile">{{
data.playerDetails.name
}}
<svg class="bi bi-link-45deg" fill="currentColor" height="16" viewBox="0 0 16 16"
width="16" xmlns="http://www.w3.org/2000/svg">
<path
d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1.002 1.002 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4.018 4.018 0 0 1-.128-1.287z"/>
<path
d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243L6.586 4.672z"/>
</svg>
</a></h3>
<table class="table table-borderless">
<tr>
<th class="text-uppercase text-muted">Wins</th>
<th class="text-uppercase text-muted">Losses</th>
<th class="text-uppercase text-muted">WinRate</th>
</tr>
<tr>
<td>{{ wl.win }}</td>
<td>{{ wl.loss }}</td>
<td>{{ (wl.win / wl.loss * 100).toFixed(2) }}%</td>
</tr>
</table>
<small v-if="data.playerDetails.tracked" class="card-text text-muted">
Currently tracked
</small>
<div v-else class="dropdown">
<button
id="login-dropdown"
aria-expanded="false"
class="btn border-2 btn-outline-info"
data-bs-toggle="dropdown"
type="button"
>
Track Me!
</button>
<div aria-labelledby="login-dropdown" class="dropdown-menu mt-2 border-2 border-primary bg-body"
style="width: 320px">
<form class="px-4 py-3">
<!-- AuthCode input -->
<div class="form-outline mb-4">
<input id="track-authcode" v-model="data.userData.authcode" class="form-control bg-secondary"
placeholder="AuthCode*"
required type="text"/>
</div>
<!-- ShareCode input -->
<div class="form-outline mb-2">
<input id="track-sharecode" v-model="data.userData.sharecode" class="form-control bg-secondary"
placeholder="ShareCode"
type="text"/>
</div>
<div class="form-outline mb-4">
<small>You can find your AuthCode and ShareCode <a
href="https://help.steampowered.com/en/wizard/HelpWithGameIssue/?appid=730&issueid=128" target="_blank">here</a>.</small>
</div>
<!-- Submit button -->
<button class="btn btn-outline-warning border-2" type="submit" @click.prevent="TrackMe">
TrackMe
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="matches m-auto">
<table v-if="data.matches" class="table table-borderless">
<thead class="border-bottom">
<tr>
<th class="text-center" scope="col">Map</th>
<th class="text-center" scope="col">Rank</th>
<th class="text-center" scope="col">Score</th>
<th class="text-center" scope="col">K</th>
<th class="text-center" scope="col">A</th>
<th class="text-center" scope="col">D</th>
<th class="text-center" scope="col" style="cursor: help" title="Kill-to-death ratio">+/-</th>
<th class="text-center" scope="col" style="cursor: help" title="Average damage per round">ADR</th>
<th class="text-center" scope="col" style="cursor: help" title="HLTV 1.0 Rating">Rating</th>
<th class="text-center" scope="col">Duration</th>
<th scope="col">Date</th>
</tr>
</thead>
<tbody>
<tr v-for="match in data.matches"
:key="match.match_id"
class="match"
@click="GoToMatch(match.match_id)">
<td class="td-map text-center">
<img :alt="match.map ? match.map : 'Map not found'"
:src="mapImg.includes(match.map) ? require('@/images/maps/' + match.map + '.png') : require('@/images/maps/not_found.png')"
:title="match.map"
class="map-icon">
</td>
<td class="td-rank text-center">
<img
:src="match.stats[0].extended?.rank?.new ? require('@/images/ranks/' + match.stats[0].extended?.rank?.new + '.png') : require('@/images/ranks/0.png')"
alt="Rank icon"
class="rank-icon">
</td>
<td :class="match.stats[0].team_id === match.match_result ? 'text-success' : !match.match_result ? 'text-warning' : 'text-danger'"
class="td-score text-center fw-bold">
{{ match.score[0] }} - {{ match.score[1] }}
</td>
<td class="td-kills text-center">
{{ match.stats[0].kills ? match.stats[0].kills : "0" }}
</td>
<td class="td-assists text-center">
{{ match.stats[0].assists ? match.stats[0].assists : "0" }}
</td>
<td class="td-deaths text-center">
{{ match.stats[0].deaths ? match.stats[0].deaths : "0" }}
</td>
<td :class="(match.stats[0].kills ? match.stats[0].kills : 0) - (match.stats[0].deaths ? match.stats[0].deaths : 0) >= 0 ? 'text-success' : 'text-danger'"
class="td-plus text-center">
{{
(match.stats[0].kills ? match.stats[0].kills : 0) - (match.stats[0].deaths ? match.stats[0].deaths : 0)
}}
</td>
<td class="td-adr text-center">
{{
Math.floor((match.stats[0].extended.dmg.total ? match.stats[0].extended.dmg.total : 0) / (match.score[0] + match.score[1]))
}}
</td>
<td :class="GetHLTV_1(
match.stats[0].kills,
match.score[0] + match.score[1],
match.stats[0].deaths,
match.stats[0].extended?.multi_kills?.duo,
match.stats[0].extended?.multi_kills?.triple,
match.stats[0].extended?.multi_kills?.quad,
match.stats[0].extended?.multi_kills?.pent) >= 1 ? 'text-success' : 'text-warning'"
class="td-hltv text-center fw-bold">
{{
GetHLTV_1(
match.stats[0].kills,
match.score[0] + match.score[1],
match.stats[0].deaths,
match.stats[0].extended?.multi_kills?.duo,
match.stats[0].extended?.multi_kills?.triple,
match.stats[0].extended?.multi_kills?.quad,
match.stats[0].extended?.multi_kills?.pent)
}}
</td>
<td class="td-duration text-center">
{{ FormatDuration(match.duration) }}
</td>
<td class="td-date">
{{ FormatDate(match.date) }}
</td>
</tr>
</tbody>
</table>
<h5 v-else>No matches on record</h5>
</div>
</div>
</div>
</template>
<script>
import {onBeforeMount, reactive, watch} from "vue";
import axios from "axios";
import {useStore} from "vuex";
import {FormatDate, FormatDuration, GetHLTV_1, GoToMatch} from "@/utils";
export default {
name: 'Player',
props: ['id'],
setup(props) {
const store = useStore()
// Vars
const mapImg = ['de_inferno', 'de_mirage', 'de_overpass']
const wl = reactive({
win: 132,
loss: 102
})
const data = reactive({
userData: {
authcode: '',
sharecode: ''
},
playerDetails: {},
matches: [],
statusError: ''
})
// Functions
const TrackMe = async () => {
const shareCodeRegex = /^CSGO(?:-?[ABCDEFGHJKLMNOPQRSTUVWXYZabcdefhijkmnopqrstuvwxyz23456789]{5}){5}$/
const authCodeRegex = /^[ABCDEFGHJKLMNOPQRSTUVWXYZ23456789]{4}-[ABCDEFGHJKLMNOPQRSTUVWXYZ23456789]{5}-[ABCDEFGHJKLMNOPQRSTUVWXYZ23456789]{4}$/
if (!shareCodeRegex.test(data.userData.sharecode))
data.statusError = 'Is not a valid sharecode'
if (!authCodeRegex.test(data.userData.authcode))
data.statusError = 'Is not a valid authcode'
const res = await axios
.post(`http://localhost:1337/http://localhost:8000/player/trackme`, `id=${store.state.id64}&authcode=${data.userData.authcode}&sharecode=${data.userData.sharecode}`)
if (res.status === 401) {
data.statusError = 'Data does not match player'
} else if (res.status === 400) {
data.statusError = 'Userinput was wrong'
} else if (res.status === 200) {
data.statusError = ''
} else {
console.log(`${res.status}: ${res.statusText}`)
}
}
const GetUser = () => {
axios
.get(`http://localhost:1337/http://localhost:8000/player/${props.id}`)
.then((response) => {
data.playerDetails = response.data
data.matches = response.data.matches
store.state.id64 = response.data.steamid64
console.log(response.data)
document.title = `${response.data.name} | CSGO-W.TF`
})
.catch((e) => {
console.log(e);
});
}
watch(() => props.id, GetUser)
onBeforeMount(() => {
GetUser()
})
return {
data, store, mapImg, wl, props, GetUser, TrackMe, FormatDate, FormatDuration, GoToMatch, GetHLTV_1
}
}
}
</script>
<style lang="scss" scoped>
.card {
padding-top: 10px;
}
td {
vertical-align: middle;
}
th:last-child, td:last-child {
text-align: right;
width: 150px;
}
.td-map {
width: 100px;
height: 100px;
}
.td-rank {
width: 88px;
height: 35px;
}
.td-score {
font-size: 1.2rem;
width: 120px;
}
.avatar {
border-radius: 50%;
height: 150px;
width: 150px;
box-shadow: 0 0 10px black;
}
.map-icon {
height: 75px;
}
.rank-icon {
height: 35px;
}
.match {
border-bottom: 1px solid rgba(73, 73, 73, 0.73);
}
.match:last-child {
border: none;
}
.match:hover {
cursor: pointer;
background: rgba(0, 0, 0, 0.25);
}
</style>