Compare commits
35 Commits
cs2-port
...
webpack-to
| Author | SHA1 | Date | |
|---|---|---|---|
| afed42de49 | |||
| 9ac3228f5d | |||
| 106ef97ede | |||
| 552188c8a9 | |||
| ce70fa2e6f | |||
| 5591e75c86 | |||
| 1236c2ca2d | |||
| 9cbbfe9393 | |||
| f6dd2ea1c4 | |||
| 70fb352d7f | |||
| 67cc06abdf | |||
| 3ad55c7fc4 | |||
| 0a355ff2bd | |||
| 16addf0bca | |||
| 5cb339483a | |||
| 7daa47bb64 | |||
| 8ed371d5fb | |||
| a45215dce1 | |||
| a03dad2a0e | |||
| 18cd1ecdc9 | |||
| 7a866c9d50 | |||
| fe7b851157 | |||
| 53225dffd4 | |||
| 0c9d6e7975 | |||
| 640eddc365 | |||
| 190064497e | |||
| d0d17ccd3d | |||
| d479573f41 | |||
| 012b56f184 | |||
| 0e727716a3 | |||
| 7523286236 | |||
| 2c3685f594 | |||
| cbe770ecd7 | |||
| 328f463cdb | |||
| 9a6d24193d |
@@ -1,3 +0,0 @@
|
|||||||
> 1%
|
|
||||||
last 2 versions
|
|
||||||
not dead
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
end_of_line = lf
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.{js,json,yml}]
|
|
||||||
charset = utf-8
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
15
.eslintrc.cjs
Normal file
15
.eslintrc.cjs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
require("@rushstack/eslint-patch/modern-module-resolution");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
"root": true,
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/vue3-essential",
|
||||||
|
"eslint:recommended",
|
||||||
|
"@vue/eslint-config-typescript/recommended",
|
||||||
|
"@vue/eslint-config-prettier"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"vue/setup-compiler-macros": true,
|
||||||
|
}
|
||||||
|
}
|
||||||
17
.eslintrc.js
17
.eslintrc.js
@@ -1,17 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
node: true
|
|
||||||
},
|
|
||||||
'extends': [
|
|
||||||
'plugin:vue/vue3-essential',
|
|
||||||
'eslint:recommended'
|
|
||||||
],
|
|
||||||
parserOptions: {
|
|
||||||
parser: 'babel-eslint'
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
### Linux ###
|
### Linux ###
|
||||||
*~
|
*~
|
||||||
|
.env
|
||||||
|
|
||||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||||
.fuse_hidden*
|
.fuse_hidden*
|
||||||
@@ -89,7 +90,7 @@ web_modules/
|
|||||||
.yarn-integrity
|
.yarn-integrity
|
||||||
|
|
||||||
# dotenv environment variables file
|
# dotenv environment variables file
|
||||||
.env
|
.env.local
|
||||||
.env.test
|
.env.test
|
||||||
.env.production
|
.env.production
|
||||||
|
|
||||||
@@ -220,7 +221,7 @@ fabric.properties
|
|||||||
# Editor-based Rest Client
|
# Editor-based Rest Client
|
||||||
.idea/httpRequests
|
.idea/httpRequests
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
# Android studio 3.1+ serialized cche file
|
||||||
.idea/caches/build_file_checksums.ser
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
### WebStorm+all Patch ###
|
### WebStorm+all Patch ###
|
||||||
@@ -283,4 +284,4 @@ $RECYCLE.BIN/
|
|||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/webstorm+all,yarn,windows,linux,node,vuejs
|
# End of https://www.toptal.com/developers/gitignore/api/webstorm+all,yarn,windows,linux,node,vuejs
|
||||||
|
|
||||||
|
a
|
||||||
|
|||||||
341
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
341
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
File diff suppressed because one or more lines are too long
631
.yarn/releases/yarn-3.0.2.cjs
vendored
631
.yarn/releases/yarn-3.0.2.cjs
vendored
File diff suppressed because one or more lines are too long
785
.yarn/releases/yarn-3.2.0.cjs
vendored
Normal file
785
.yarn/releases/yarn-3.2.0.cjs
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -4,4 +4,4 @@ plugins:
|
|||||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||||
spec: "@yarnpkg/plugin-interactive-tools"
|
spec: "@yarnpkg/plugin-interactive-tools"
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-3.0.2.cjs
|
yarnPath: .yarn/releases/yarn-3.2.0.cjs
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
'@vue/cli-plugin-babel/preset'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta content="IE=edge" http-equiv="X-UA-Compatible">
|
<meta content="IE=edge" http-equiv="X-UA-Compatible">
|
||||||
<meta content="width=device-width,initial-scale=1.0" name="viewport">
|
<meta content="width=device-width,initial-scale=1.0" name="viewport">
|
||||||
@@ -40,11 +40,11 @@
|
|||||||
<meta content="https://csgow.tf/images/logo.png"
|
<meta content="https://csgow.tf/images/logo.png"
|
||||||
property="og:image:secure_url">
|
property="og:image:secure_url">
|
||||||
|
|
||||||
<link href="<%= BASE_URL %>images/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180">
|
<link href="/images/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180">
|
||||||
<link href="<%= BASE_URL %>images/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png">
|
<link href="/images/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png">
|
||||||
<link href="<%= BASE_URL %>images/favicon-16x16.png" rel="icon" sizes="16x16" type="image/png">
|
<link href="/images/favicon-16x16.png" rel="icon" sizes="16x16" type="image/png">
|
||||||
|
|
||||||
<link href="<%= BASE_URL %>site.webmanifest" rel="manifest">
|
<link href="/site.webmanifest" rel="manifest">
|
||||||
|
|
||||||
<link rel="preconnect" href="https://steamcdn-a.akamaihd.net" crossorigin>
|
<link rel="preconnect" href="https://steamcdn-a.akamaihd.net" crossorigin>
|
||||||
<link rel="dns-prefetch" href="https://steamcdn-a.akamaihd.net">
|
<link rel="dns-prefetch" href="https://steamcdn-a.akamaihd.net">
|
||||||
@@ -53,14 +53,10 @@
|
|||||||
<link rel="preconnect" href="https://piwik.harting.hosting" crossorigin>
|
<link rel="preconnect" href="https://piwik.harting.hosting" crossorigin>
|
||||||
<link rel="dns-prefetch" href="https://piwik.harting.hosting">
|
<link rel="dns-prefetch" href="https://piwik.harting.hosting">
|
||||||
|
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<title>csgoWTF</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<div id="app"></div>
|
||||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
|
<script type="module" src="/src/main.ts"></script>
|
||||||
Please enable it to continue.</strong>
|
</body>
|
||||||
</noscript>
|
|
||||||
<div id="app" class="d-flex flex-column min-vh-100"></div>
|
|
||||||
<!-- built files will be auto injected -->
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
56
package.json
56
package.json
@@ -1,43 +1,49 @@
|
|||||||
{
|
{
|
||||||
"name": "csgowtf",
|
"name": "csgowtf",
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"dev": "vite",
|
||||||
"build": "vue-cli-service build --mode production",
|
"host": "vite --host",
|
||||||
"lint": "vue-cli-service lint"
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
|
"preview": "vite preview --port 5050",
|
||||||
|
"typecheck": "vue-tsc --noEmit",
|
||||||
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@popperjs/core": "^2.11.2",
|
"@popperjs/core": "^2.11.4",
|
||||||
"axios": "^0.25.0",
|
"axios": "^0.26.1",
|
||||||
"bootstrap": "^5.1.3",
|
"bootstrap": "^5.1.3",
|
||||||
"core-js": "^3.21.0",
|
"bootstrap-icons": "^1.8.1",
|
||||||
"dotenv-webpack": "^7.1.0",
|
"csgo-sharecode": "^3.0.1",
|
||||||
"echarts": "^5.3.0",
|
"echarts": "^5.3.1",
|
||||||
"fork-awesome": "^1.2.0",
|
"fork-awesome": "^1.2.0",
|
||||||
"http-status-codes": "^2.2.0",
|
"http-status-codes": "^2.2.0",
|
||||||
"iso-639-1": "^2.1.13",
|
"iso-639-1": "^2.1.13",
|
||||||
"jquery": "^3.6.0",
|
"jquery": "^3.6.0",
|
||||||
"luxon": "^2.3.0",
|
"luxon": "^2.3.1",
|
||||||
"string-sanitizer": "^2.0.2",
|
"pinia": "^2.0.12",
|
||||||
"vue": "^3.2.30",
|
"vue": "^3.2.31",
|
||||||
"vue-matomo": "^4.1.0",
|
"vue-matomo": "^4.1.0",
|
||||||
"vue-router": "^4.0.12",
|
"vue-router": "^4.0.14",
|
||||||
"vue3-cookies": "^1.0.6",
|
"vue3-cookies": "^1.0.6",
|
||||||
"vuex": "^4.0.2"
|
"vuex": "^4.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-babel": "~4.5.15",
|
"@rushstack/eslint-patch": "^1.1.1",
|
||||||
"@vue/cli-plugin-eslint": "~4.5.15",
|
"@types/echarts": "^4.9.13",
|
||||||
"@vue/cli-plugin-router": "~4.5.15",
|
"@types/luxon": "^2.3.1",
|
||||||
"@vue/cli-plugin-vuex": "~4.5.15",
|
"@types/node": "^16.11.26",
|
||||||
"@vue/cli-service": "~4.5.15",
|
"@vitejs/plugin-vue": "^2.2.4",
|
||||||
"@vue/compiler-sfc": "^3.2.30",
|
"@vue/eslint-config-prettier": "^7.0.0",
|
||||||
"babel-eslint": "^10.1.0",
|
"@vue/eslint-config-typescript": "^10.0.0",
|
||||||
"eslint": "^6.8.0",
|
"@vue/tsconfig": "^0.1.3",
|
||||||
"eslint-plugin-vue": "^7.20.0",
|
"eslint": "^8.11.0",
|
||||||
"sass": "^1.49.7",
|
"eslint-plugin-vue": "^8.5.0",
|
||||||
"sass-loader": "^10.2.1"
|
"prettier": "^2.6.0",
|
||||||
|
"sass": "^1.49.9",
|
||||||
|
"typescript": "~4.6.2",
|
||||||
|
"vite": "^2.8.6",
|
||||||
|
"vue-tsc": "^0.33.2"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@3.0.2"
|
"packageManager": "yarn@3.2.0"
|
||||||
}
|
}
|
||||||
|
|||||||
67
src/App.vue
67
src/App.vue
@@ -1,58 +1,53 @@
|
|||||||
<template>
|
<template>
|
||||||
<img alt="" class="bg-img" src="">
|
<img alt="" class="bg-img" src="" />
|
||||||
<header>
|
<header>
|
||||||
<Nav/>
|
<Nav />
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<div :style="{height: offset + 'px'}"/>
|
<div :style="{ height: offset + 'px' }" />
|
||||||
<InfoModal/>
|
<InfoModal />
|
||||||
<router-view name="main"/>
|
<router-view name="main" />
|
||||||
</main>
|
</main>
|
||||||
<footer class="mt-auto">
|
<footer class="mt-auto">
|
||||||
<Footer/>
|
<Footer />
|
||||||
</footer>
|
</footer>
|
||||||
<CookieConsentBtn id="cookie-btn"/>
|
<CookieConsentBtn id="cookie-btn" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import Nav from "@/components/Nav";
|
import Nav from "@/components/NavComponent.vue";
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/FooterComponent.vue";
|
||||||
import CookieConsentBtn from "@/components/CookieConsentBtn";
|
import CookieConsentBtn from "@/components/CookieConsentBtn.vue";
|
||||||
import {onMounted, ref} from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import InfoModal from "@/components/InfoModal";
|
import InfoModal from "@/components/InfoModal.vue";
|
||||||
|
|
||||||
export default {
|
const offset = ref(0);
|
||||||
components: {InfoModal, Footer, Nav, CookieConsentBtn},
|
|
||||||
setup() {
|
|
||||||
const offset = ref(0)
|
|
||||||
|
|
||||||
const setOffset = () => {
|
const setOffset = () => {
|
||||||
return document.getElementsByTagName('nav')[0].clientHeight
|
return document.getElementsByTagName("nav")[0].clientHeight;
|
||||||
}
|
};
|
||||||
|
|
||||||
const setBgHeight = () => {
|
const setBgHeight = () => {
|
||||||
document.querySelector('.bg-img').style.height = document.documentElement.clientHeight + 'px'
|
const bgImg = document.querySelector(".bg-img") as HTMLImageElement;
|
||||||
}
|
bgImg.style.height = document.documentElement.clientHeight + "px" || "0px";
|
||||||
|
};
|
||||||
|
|
||||||
window.onresize = () => {
|
window.onresize = () => {
|
||||||
offset.value = setOffset()
|
offset.value = setOffset();
|
||||||
setBgHeight()
|
setBgHeight();
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
offset.value = setOffset()
|
offset.value = setOffset();
|
||||||
setBgHeight()
|
setBgHeight();
|
||||||
})
|
});
|
||||||
|
|
||||||
return {offset}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Obitron";
|
font-family: "Obitron";
|
||||||
src: local("Obitron"), url("../public/fonts/Orbitron-VariableFont_wght.ttf") format("truetype");
|
src: local("Obitron"),
|
||||||
|
url("../public/fonts/Orbitron-VariableFont_wght.ttf") format("truetype");
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-img {
|
.bg-img {
|
||||||
|
|||||||
@@ -1,66 +1,87 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="!consent" class="card text-end bg-secondary text-white border border-1">
|
<div
|
||||||
|
v-if="!consent"
|
||||||
|
class="card text-end bg-secondary text-white border border-1"
|
||||||
|
>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form class="mb-1">
|
<form class="mb-1">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input id="essential-cookies" checked class="form-check-input" disabled type="checkbox" value="">
|
<input
|
||||||
|
id="essential-cookies"
|
||||||
|
checked
|
||||||
|
class="form-check-input"
|
||||||
|
disabled
|
||||||
|
type="checkbox"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
<label class="form-check-label" for="essential-cookies">
|
<label class="form-check-label" for="essential-cookies">
|
||||||
Essential
|
Essential
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input id="tracking" v-model="tracking" class="form-check-input" type="checkbox">
|
<input
|
||||||
<label class="form-check-label" for="tracking">
|
id="tracking"
|
||||||
Matomo
|
v-model="tracking"
|
||||||
</label>
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="tracking"> Matomo </label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<a href="/privacy-policy" class="text-muted">Privacy Policy</a>
|
<a class="text-muted" href="/privacy-policy">Privacy Policy</a>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between mt-2">
|
<div class="d-flex justify-content-between mt-2">
|
||||||
<button class="btn btn-outline-primary" type="button" @click="handleConsentForget">Decline</button>
|
<button
|
||||||
<button class="btn btn-info" type="button" @click="handleConsent">Accept</button>
|
class="btn btn-outline-primary"
|
||||||
|
type="button"
|
||||||
|
@click="handleConsentForget"
|
||||||
|
>
|
||||||
|
Decline
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-info" type="button" @click="handleConsent">
|
||||||
|
Accept
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import {onMounted, ref} from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import {useCookies} from 'vue3-cookies'
|
import { useCookies } from "vue3-cookies";
|
||||||
|
|
||||||
export default {
|
const tracking = ref(true);
|
||||||
name: "CookieConsentBtn",
|
const { cookies } = useCookies();
|
||||||
setup() {
|
const consent = ref(false);
|
||||||
const tracking = ref(true)
|
|
||||||
const {cookies} = useCookies()
|
|
||||||
const consent = ref(false)
|
|
||||||
|
|
||||||
const handleConsent = () => {
|
const handleConsent = () => {
|
||||||
window._paq.push(['rememberCookieConsentGiven'])
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
cookies.set('consent', 'given', Infinity)
|
// @ts-ignore
|
||||||
|
window._paq.push(["rememberCookieConsentGiven"]);
|
||||||
|
cookies.set("consent", "given", Infinity);
|
||||||
|
|
||||||
if (tracking.value){
|
if (tracking.value) {
|
||||||
window._paq.push(['rememberConsentGiven'])
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
}
|
// @ts-ignore
|
||||||
consent.value = true
|
window._paq.push(["rememberConsentGiven"]);
|
||||||
}
|
|
||||||
const handleConsentForget = () => {
|
|
||||||
consent.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
window._paq.push(['requireCookieConsent']);
|
|
||||||
window._paq.push(['trackPageView']);
|
|
||||||
|
|
||||||
if (cookies.get('consent') === 'given')
|
|
||||||
consent.value = true
|
|
||||||
})
|
|
||||||
|
|
||||||
return {handleConsent, handleConsentForget, tracking, consent}
|
|
||||||
}
|
}
|
||||||
}
|
consent.value = true;
|
||||||
|
};
|
||||||
|
const handleConsentForget = () => {
|
||||||
|
consent.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
window._paq.push(["requireCookieConsent"]);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
window._paq.push(["trackPageView"]);
|
||||||
|
|
||||||
|
if (cookies.get("consent") === "given") consent.value = true;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -2,48 +2,44 @@
|
|||||||
<div class="damage-site">
|
<div class="damage-site">
|
||||||
<div class="total-damage">
|
<div class="total-damage">
|
||||||
<h3 class="text-center mt-2">Total Damage</h3>
|
<h3 class="text-center mt-2">Total Damage</h3>
|
||||||
<TotalDamage/>
|
<TotalDamage />
|
||||||
</div>
|
</div>
|
||||||
<div class="hitgroup">
|
<div class="hitgroup">
|
||||||
<!-- <h3 class="text-center">Damage by Hitgroup</h3>-->
|
<!-- <h3 class="text-center">Damage by Hitgroup</h3>-->
|
||||||
<HitgroupPuppet :equipment_map="data.equipment_map" :stats="data.stats" />
|
<HitgroupPuppet :equipment_map="data.equipment_map" :stats="data.stats" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import HitgroupPuppet from '@/components/HitgroupPuppet'
|
import HitgroupPuppet from "@/components/HitgroupPuppet.vue";
|
||||||
import TotalDamage from "@/components/TotalDamage"
|
import TotalDamage from "@/components/TotalDamage.vue";
|
||||||
import {onMounted, reactive} from "vue";
|
import { onMounted } from "vue";
|
||||||
import {useStore} from "vuex";
|
import { GetWeaponDmg } from "@/utils";
|
||||||
import {GetWeaponDmg} from "@/utils";
|
import { useMatchDetailsStore } from "@/stores/matchDetails";
|
||||||
|
import type { MatchWeapons } from "@/types";
|
||||||
|
import { useInfoStateStore } from "@/stores/infoState";
|
||||||
|
|
||||||
export default {
|
const data = {} as MatchWeapons;
|
||||||
name: "DamageSite.vue",
|
|
||||||
components: {HitgroupPuppet, TotalDamage},
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const data = reactive({
|
const matchDetailsStore = useMatchDetailsStore();
|
||||||
equipment_map: {},
|
const infoStateStore = useInfoStateStore();
|
||||||
stats: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
const getWeaponDamage = async () => {
|
const getWeaponDamage = async () => {
|
||||||
const resData = await GetWeaponDmg(store, store.state.matchDetails.match_id)
|
const [resData, info] = await GetWeaponDmg(
|
||||||
if (resData !== null) {
|
matchDetailsStore.matchDetails.match_id
|
||||||
data.equipment_map = resData.equipment_map
|
);
|
||||||
data.stats = resData.stats
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
if (info.message !== "") infoStateStore.addInfo(info);
|
||||||
getWeaponDamage()
|
if (resData !== null) {
|
||||||
})
|
data.equipment_map = resData.equipment_map;
|
||||||
|
data.stats = resData.stats;
|
||||||
return {data}
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getWeaponDamage();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="details-site">
|
|
||||||
<div class="multi-kills">
|
|
||||||
<h3 class="text-center mt-2">Multi-Kills</h3>
|
|
||||||
<MultiKillsChart/>
|
|
||||||
</div>
|
|
||||||
<!-- <hr>-->
|
|
||||||
<!-- <div class="spray">-->
|
|
||||||
<!-- <h3 class="text-center">Spray</h3>-->
|
|
||||||
<!-- <SprayGraph :spray="data.spray"/>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import MultiKillsChart from "@/components/MultiKillsChart";
|
|
||||||
import {useStore} from "vuex";
|
|
||||||
import {onMounted, reactive} from "vue";
|
|
||||||
import {GetWeaponDmg} from "@/utils";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "Details",
|
|
||||||
components: {MultiKillsChart},
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const data = reactive({
|
|
||||||
spray: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
const getWeaponDamage = async () => {
|
|
||||||
const resData = await GetWeaponDmg(store, store.state.matchDetails.match_id)
|
|
||||||
if (resData !== null) {
|
|
||||||
data.spray = resData.spray
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getWeaponDamage()
|
|
||||||
})
|
|
||||||
|
|
||||||
return {data}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.details-site {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
60
src/components/DetailsComponent.vue
Normal file
60
src/components/DetailsComponent.vue
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<template>
|
||||||
|
<div class="details-site">
|
||||||
|
<div class="multi-kills">
|
||||||
|
<h3 class="text-center mt-2">Multi-Kills</h3>
|
||||||
|
<MultiKillsChart />
|
||||||
|
</div>
|
||||||
|
<!-- <hr>-->
|
||||||
|
<!-- <div class="spray">-->
|
||||||
|
<!-- <h3 class="text-center">Spray</h3>-->
|
||||||
|
<!-- <SprayGraph :spray="data.spray"/>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import MultiKillsChart from "@/components/MultiKillsChart.vue";
|
||||||
|
import { onMounted } from "vue";
|
||||||
|
import { GetWeaponDmg } from "@/utils";
|
||||||
|
import { useMatchDetailsStore } from "@/stores/matchDetails";
|
||||||
|
import { useInfoStateStore } from "@/stores/infoState";
|
||||||
|
import type { MatchWeapons } from "@/types";
|
||||||
|
|
||||||
|
const matchDetailsStore = useMatchDetailsStore();
|
||||||
|
const infoStateStore = useInfoStateStore();
|
||||||
|
|
||||||
|
const data = {} as MatchWeapons;
|
||||||
|
|
||||||
|
const getWeaponDamage = async () => {
|
||||||
|
const [resData, info] = await GetWeaponDmg(
|
||||||
|
matchDetailsStore.matchDetails.match_id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (info.message !== "") infoStateStore.addInfo(info);
|
||||||
|
if (resData !== null) {
|
||||||
|
data.spray = resData.spray;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getWeaponDamage();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.details-site {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,244 +6,275 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { GetPlayerValue } from "@/utils";
|
||||||
|
import {
|
||||||
|
onBeforeMount,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
|
reactive,
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
} from "vue";
|
||||||
|
|
||||||
import {GetPlayerValue} from "@/utils";
|
import * as echarts from "echarts/core";
|
||||||
import {useStore} from "vuex";
|
|
||||||
import {onBeforeMount, onMounted, onUnmounted, reactive, ref, watch} from "vue";
|
|
||||||
|
|
||||||
import * as echarts from 'echarts/core';
|
|
||||||
import {
|
import {
|
||||||
GridComponent,
|
GridComponent,
|
||||||
MarkAreaComponent,
|
MarkAreaComponent,
|
||||||
TitleComponent,
|
TitleComponent,
|
||||||
TooltipComponent,
|
TooltipComponent,
|
||||||
VisualMapComponent
|
VisualMapComponent,
|
||||||
} from 'echarts/components';
|
} from "echarts/components";
|
||||||
import {LineChart} from 'echarts/charts';
|
import { LineChart } from "echarts/charts";
|
||||||
import {UniversalTransition} from 'echarts/features';
|
import { UniversalTransition } from "echarts/features";
|
||||||
import {CanvasRenderer} from 'echarts/renderers';
|
import { CanvasRenderer } from "echarts/renderers";
|
||||||
|
import type { MatchRounds, MatchStats } from "@/types";
|
||||||
|
import { useMatchDetailsStore } from "@/stores/matchDetails";
|
||||||
|
import { useInfoStateStore } from "@/stores/infoState";
|
||||||
|
|
||||||
export default {
|
const matchDetailsStore = useMatchDetailsStore();
|
||||||
name: "EqValueGraph",
|
const infoStateStore = useInfoStateStore();
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
let myChart1, max_rounds
|
let myChart1: echarts.ECharts, max_rounds: echarts.ECharts;
|
||||||
let valueList = []
|
let valueList: any[] = [];
|
||||||
let dataList = []
|
let dataList: any[] = [];
|
||||||
const width = ref(window.innerWidth >= 800 && window.innerWidth <= 1200 ? window.innerWidth : window.innerWidth < 800 ? 800 : 1200)
|
const width = ref(
|
||||||
const height = ref(width.value * 1 / 3)
|
window.innerWidth >= 800 && window.innerWidth <= 1200
|
||||||
|
? window.innerWidth
|
||||||
|
: window.innerWidth < 800
|
||||||
|
? 800
|
||||||
|
: 1200
|
||||||
|
);
|
||||||
|
const height = ref((width.value * 1) / 3);
|
||||||
|
|
||||||
const data = reactive({
|
interface eqTeamPlayer {
|
||||||
rounds: {},
|
round: string;
|
||||||
team: [],
|
player: string;
|
||||||
eq_team_1: [],
|
eq: number;
|
||||||
eq_team_2: [],
|
}
|
||||||
eq_team_player_1: [],
|
|
||||||
eq_team_player_2: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
const getTeamPlayer = (stats, team) => {
|
const data = reactive({
|
||||||
let arr = []
|
rounds: {} as MatchRounds,
|
||||||
for (let i = (team - 1) * 5; i < team * 5; i++) {
|
team: [],
|
||||||
arr.push(stats[i].player.steamid64)
|
eq_team_1: [],
|
||||||
|
eq_team_2: [],
|
||||||
|
eq_team_player_1: [] as eqTeamPlayer[],
|
||||||
|
eq_team_player_2: [] as eqTeamPlayer[],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getTeamPlayer = (stats: MatchStats[], team: number) => {
|
||||||
|
let arr = [];
|
||||||
|
for (let i = (team - 1) * 5; i < team * 5; i++) {
|
||||||
|
const player = stats[i];
|
||||||
|
arr.push(player?.player?.steamid64);
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseObject = async () => {
|
||||||
|
const [res, info] = await GetPlayerValue(
|
||||||
|
matchDetailsStore.matchDetails.match_id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (info.message !== "") infoStateStore.addInfo(info);
|
||||||
|
if (res !== null) data.rounds = res;
|
||||||
|
|
||||||
|
for (const round in data.rounds) {
|
||||||
|
for (const player in data.rounds[round]) {
|
||||||
|
for (let p in data.team[0]) {
|
||||||
|
if (data.team[0][p] === player) {
|
||||||
|
data.eq_team_player_1.push({
|
||||||
|
round: round,
|
||||||
|
player: player,
|
||||||
|
eq: data.rounds[round][player][0] + data.rounds[round][player][0],
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
for (let p in data.team[1]) {
|
||||||
return arr
|
if (data.team[1][p] === player) {
|
||||||
}
|
data.eq_team_player_2.push({
|
||||||
|
round: round,
|
||||||
const parseObject = async () => {
|
player: player,
|
||||||
data.rounds = await GetPlayerValue(store, store.state.matchDetails.match_id)
|
eq: data.rounds[round][player][0] + data.rounds[round][player][2],
|
||||||
if (data.rounds === null)
|
});
|
||||||
data.rounds = {}
|
|
||||||
|
|
||||||
for (const round in data.rounds) {
|
|
||||||
for (const player in data.rounds[round]) {
|
|
||||||
for (let p in data.team[0]) {
|
|
||||||
if (data.team[0][p] === player) {
|
|
||||||
data.eq_team_player_1.push({
|
|
||||||
round: round,
|
|
||||||
player: player,
|
|
||||||
eq: (data.rounds[round][player][0] + data.rounds[round][player][2])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let p in data.team[1]) {
|
|
||||||
if (data.team[1][p] === player) {
|
|
||||||
data.eq_team_player_2.push({
|
|
||||||
round: round,
|
|
||||||
player: player,
|
|
||||||
eq: (data.rounds[round][player][0] + data.rounds[round][player][2])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sumArr = (arr) => {
|
|
||||||
return arr.reduce((acc, current) => ({
|
|
||||||
...acc,
|
|
||||||
[current.round]: (acc[current.round] || 0) + current.eq
|
|
||||||
}), {})
|
|
||||||
}
|
|
||||||
|
|
||||||
const BuildGraphData = (team_1, team_2, max_rounds) => {
|
|
||||||
let newArr = []
|
|
||||||
const half_point = max_rounds / 2 - 1
|
|
||||||
for (let round in team_1) {
|
|
||||||
if (round <= half_point) {
|
|
||||||
newArr.push(team_1[round] - team_2[round])
|
|
||||||
} else
|
|
||||||
newArr.push(team_2[round] - team_1[round])
|
|
||||||
}
|
|
||||||
return newArr
|
|
||||||
}
|
|
||||||
|
|
||||||
const optionGen = (dataList, valueList) => {
|
|
||||||
return {
|
|
||||||
// Make gradient line here
|
|
||||||
visualMap: [
|
|
||||||
{
|
|
||||||
show: false,
|
|
||||||
type: 'continuous',
|
|
||||||
seriesIndex: 0,
|
|
||||||
color: ['#3a6e99', '#c3a235'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
formatter: 'Round <b>{b0}</b><br />{a0} <b>{c0}</b>',
|
|
||||||
},
|
|
||||||
xAxis: [
|
|
||||||
{
|
|
||||||
type: 'category',
|
|
||||||
data: dataList,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
yAxis: [
|
|
||||||
{},
|
|
||||||
],
|
|
||||||
grid: [
|
|
||||||
{
|
|
||||||
bottom: '10%'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
top: '0%'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
right: '0%'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
left: '0%'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'Net-Worth',
|
|
||||||
type: 'line',
|
|
||||||
lineStyle: {
|
|
||||||
width: 4
|
|
||||||
},
|
|
||||||
showSymbol: false,
|
|
||||||
data: valueList,
|
|
||||||
markArea: {
|
|
||||||
data: [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'Half-Point',
|
|
||||||
xAxis: max_rounds / 2 - 1,
|
|
||||||
label: {
|
|
||||||
color: 'white'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
xAxis: max_rounds / 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
itemStyle: {
|
|
||||||
color: 'rgba(200,200,200, 0.3)'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const disposeCharts = () => {
|
|
||||||
if (myChart1 != null && myChart1 !== '' && myChart1 !== undefined) {
|
|
||||||
myChart1.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildCharts = () => {
|
|
||||||
disposeCharts()
|
|
||||||
|
|
||||||
myChart1 = echarts.init(document.getElementById('economy-graph'), {}, {
|
|
||||||
width: width.value,
|
|
||||||
height: height.value
|
|
||||||
})
|
|
||||||
myChart1.setOption(optionGen(dataList, valueList))
|
|
||||||
}
|
|
||||||
|
|
||||||
onBeforeMount(() => {
|
|
||||||
max_rounds = store.state.matchDetails.max_rounds ? store.state.matchDetails.max_rounds : 30
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (store.state.matchDetails.stats) {
|
|
||||||
echarts.use([
|
|
||||||
TitleComponent,
|
|
||||||
TooltipComponent,
|
|
||||||
GridComponent,
|
|
||||||
VisualMapComponent,
|
|
||||||
LineChart,
|
|
||||||
CanvasRenderer,
|
|
||||||
UniversalTransition,
|
|
||||||
MarkAreaComponent
|
|
||||||
]);
|
|
||||||
|
|
||||||
data.team.push(getTeamPlayer(store.state.matchDetails.stats, 1))
|
|
||||||
data.team.push(getTeamPlayer(store.state.matchDetails.stats, 2))
|
|
||||||
|
|
||||||
parseObject()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
disposeCharts()
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => data.rounds, () => {
|
|
||||||
data.eq_team_1 = sumArr(data.eq_team_player_1)
|
|
||||||
data.eq_team_2 = sumArr(data.eq_team_player_2)
|
|
||||||
|
|
||||||
valueList = BuildGraphData(data.eq_team_1, data.eq_team_2, max_rounds)
|
|
||||||
|
|
||||||
dataList = Array.from(Array(valueList.length + 1).keys())
|
|
||||||
dataList.shift()
|
|
||||||
|
|
||||||
buildCharts()
|
|
||||||
})
|
|
||||||
|
|
||||||
window.onresize = () => {
|
|
||||||
if (window.innerWidth > 1200) {
|
|
||||||
width.value = 1200
|
|
||||||
}
|
|
||||||
if (window.innerWidth <= 1200 && window.innerWidth >= 800) {
|
|
||||||
width.value = window.innerWidth - 20
|
|
||||||
}
|
|
||||||
if (window.innerWidth < 800) {
|
|
||||||
width.value = 800
|
|
||||||
}
|
|
||||||
|
|
||||||
height.value = width.value * 1 / 3
|
|
||||||
buildCharts()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// TODO: REWORK
|
||||||
|
|
||||||
|
const sumArr = (arr: eqTeamPlayer[]) => {
|
||||||
|
return arr.reduce(
|
||||||
|
(acc, current) => ({
|
||||||
|
...acc,
|
||||||
|
[current.round]: (acc[current.round] || 0) + current.eq,
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const BuildGraphData = (team_1, team_2, max_rounds) => {
|
||||||
|
let newArr = [];
|
||||||
|
const half_point = max_rounds / 2 - 1;
|
||||||
|
for (let round in team_1) {
|
||||||
|
if (round <= half_point) {
|
||||||
|
newArr.push(team_1[round] - team_2[round]);
|
||||||
|
} else newArr.push(team_2[round] - team_1[round]);
|
||||||
|
}
|
||||||
|
return newArr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const optionGen = (dataList, valueList) => {
|
||||||
|
return {
|
||||||
|
// Make gradient line here
|
||||||
|
visualMap: [
|
||||||
|
{
|
||||||
|
show: false,
|
||||||
|
type: "continuous",
|
||||||
|
seriesIndex: 0,
|
||||||
|
color: ["#3a6e99", "#c3a235"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
trigger: "axis",
|
||||||
|
formatter: "Round <b>{b0}</b><br />{a0} <b>{c0}</b>",
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
data: dataList,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
yAxis: [{}],
|
||||||
|
grid: [
|
||||||
|
{
|
||||||
|
bottom: "10%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
top: "0%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
right: "0%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: "0%",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "Net-Worth",
|
||||||
|
type: "line",
|
||||||
|
lineStyle: {
|
||||||
|
width: 4,
|
||||||
|
},
|
||||||
|
showSymbol: false,
|
||||||
|
data: valueList,
|
||||||
|
markArea: {
|
||||||
|
data: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: "Half-Point",
|
||||||
|
xAxis: max_rounds / 2 - 1,
|
||||||
|
label: {
|
||||||
|
color: "white",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xAxis: max_rounds / 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
itemStyle: {
|
||||||
|
color: "rgba(200,200,200, 0.3)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const disposeCharts = () => {
|
||||||
|
if (myChart1 != null && myChart1 !== "" && myChart1 !== undefined) {
|
||||||
|
myChart1.dispose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildCharts = () => {
|
||||||
|
disposeCharts();
|
||||||
|
|
||||||
|
myChart1 = echarts.init(
|
||||||
|
document.getElementById("economy-graph"),
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
width: width.value,
|
||||||
|
height: height.value,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
myChart1.setOption(optionGen(dataList, valueList));
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
max_rounds = store.state.matchDetails.max_rounds
|
||||||
|
? store.state.matchDetails.max_rounds
|
||||||
|
: 30;
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (store.state.matchDetails.stats) {
|
||||||
|
echarts.use([
|
||||||
|
TitleComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
GridComponent,
|
||||||
|
VisualMapComponent,
|
||||||
|
LineChart,
|
||||||
|
CanvasRenderer,
|
||||||
|
UniversalTransition,
|
||||||
|
MarkAreaComponent,
|
||||||
|
]);
|
||||||
|
|
||||||
|
data.team.push(getTeamPlayer(store.state.matchDetails.stats, 1));
|
||||||
|
data.team.push(getTeamPlayer(store.state.matchDetails.stats, 2));
|
||||||
|
|
||||||
|
parseObject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
disposeCharts();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => data.rounds,
|
||||||
|
() => {
|
||||||
|
data.eq_team_1 = sumArr(data.eq_team_player_1);
|
||||||
|
data.eq_team_2 = sumArr(data.eq_team_player_2);
|
||||||
|
|
||||||
|
valueList = BuildGraphData(data.eq_team_1, data.eq_team_2, max_rounds);
|
||||||
|
|
||||||
|
dataList = Array.from(Array(valueList.length + 1).keys());
|
||||||
|
dataList.shift();
|
||||||
|
|
||||||
|
buildCharts();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
window.onresize = () => {
|
||||||
|
if (window.innerWidth > 1200) {
|
||||||
|
width.value = 1200;
|
||||||
|
}
|
||||||
|
if (window.innerWidth <= 1200 && window.innerWidth >= 800) {
|
||||||
|
width.value = window.innerWidth - 20;
|
||||||
|
}
|
||||||
|
if (window.innerWidth < 800) {
|
||||||
|
width.value = 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
height.value = (width.value * 1) / 3;
|
||||||
|
buildCharts();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -7,14 +7,22 @@
|
|||||||
<table class="table table-borderless text-muted">
|
<table class="table table-borderless text-muted">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<span class="text-uppercase float-end" :class="toggle === 'duration' ? 'text-warning' : ''">Duration</span>
|
<span
|
||||||
|
:class="toggle === 'duration' ? 'text-warning' : ''"
|
||||||
|
class="text-uppercase float-end"
|
||||||
|
>Duration</span
|
||||||
|
>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<i id="toggle-off" class="fa fa-toggle-off show"></i>
|
<i id="toggle-off" class="fa fa-toggle-off show"></i>
|
||||||
<i id="toggle-on" class="fa fa-toggle-on"></i>
|
<i id="toggle-on" class="fa fa-toggle-on"></i>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="text-uppercase float-start" :class="toggle === 'total' ? 'text-warning' : ''">Count</span>
|
<span
|
||||||
|
:class="toggle === 'total' ? 'text-warning' : ''"
|
||||||
|
class="text-uppercase float-start"
|
||||||
|
>Count</span
|
||||||
|
>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -27,123 +35,146 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as echarts from 'echarts/core';
|
// TODO: REWORK
|
||||||
import {GridComponent, LegendComponent, TooltipComponent} from 'echarts/components';
|
import * as echarts from "echarts/core";
|
||||||
import {BarChart} from 'echarts/charts';
|
import {
|
||||||
import {CanvasRenderer} from 'echarts/renderers';
|
GridComponent,
|
||||||
import {onMounted, onUnmounted, ref, watch} from "vue";
|
LegendComponent,
|
||||||
import {checkStatEmpty, getPlayerArr} from "@/utils";
|
TooltipComponent,
|
||||||
import {useStore} from "vuex";
|
} from "echarts/components";
|
||||||
|
import { BarChart } from "echarts/charts";
|
||||||
|
import { CanvasRenderer } from "echarts/renderers";
|
||||||
|
import { onMounted, onUnmounted, ref, watch } from "vue";
|
||||||
|
import { checkStatEmpty, getPlayerArr } from "/src/utils";
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "FlashChart",
|
name: "FlashChart",
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore()
|
const store = useStore();
|
||||||
|
|
||||||
const toggle = ref('duration')
|
const toggle = ref("duration");
|
||||||
let myChart1, myChart2
|
let myChart1, myChart2;
|
||||||
const color = ['#bb792c', '#9bd270', '#eac42a']
|
const color = ["#bb792c", "#9bd270", "#eac42a"];
|
||||||
const width = ref(window.innerWidth <= 600 ? window.innerWidth : 600)
|
const width = ref(window.innerWidth <= 600 ? window.innerWidth : 600);
|
||||||
const height = ref(width.value * 2 / 3)
|
const height = ref((width.value * 2) / 3);
|
||||||
|
|
||||||
const toggleShow = () => {
|
const toggleShow = () => {
|
||||||
const offBtn = document.getElementById('toggle-off')
|
const offBtn = document.getElementById("toggle-off");
|
||||||
const onBtn = document.getElementById('toggle-on')
|
const onBtn = document.getElementById("toggle-on");
|
||||||
|
|
||||||
if (offBtn.classList.contains('show')) {
|
if (offBtn.classList.contains("show")) {
|
||||||
offBtn.classList.remove('show')
|
offBtn.classList.remove("show");
|
||||||
onBtn.classList.add('show')
|
onBtn.classList.add("show");
|
||||||
toggle.value = 'total'
|
toggle.value = "total";
|
||||||
} else if (onBtn.classList.contains('show')) {
|
} else if (onBtn.classList.contains("show")) {
|
||||||
onBtn.classList.remove('show')
|
onBtn.classList.remove("show");
|
||||||
offBtn.classList.add('show')
|
offBtn.classList.add("show");
|
||||||
toggle.value = 'duration'
|
toggle.value = "duration";
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const valueArr = (stats, team, toggle, prop) => {
|
const valueArr = (stats, team, toggle, prop) => {
|
||||||
if (['team', 'enemy', 'self'].indexOf(prop) > -1) {
|
if (["team", "enemy", "self"].indexOf(prop) > -1) {
|
||||||
let arr = []
|
let arr = [];
|
||||||
for (let i = (team - 1) * 5; i < team * 5; i++) {
|
for (let i = (team - 1) * 5; i < team * 5; i++) {
|
||||||
arr.push(checkStatEmpty(Function('return(function(stats, i){ return stats[i].flash.' + toggle.value + '.' + prop + '})')()(stats, i)).toFixed(2))
|
arr.push(
|
||||||
|
checkStatEmpty(
|
||||||
|
Function(
|
||||||
|
"return(function(stats, i){ return stats[i].flash." +
|
||||||
|
toggle.value +
|
||||||
|
"." +
|
||||||
|
prop +
|
||||||
|
"})"
|
||||||
|
)()(stats, i)
|
||||||
|
).toFixed(2)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
arr.reverse()
|
arr.reverse();
|
||||||
|
|
||||||
return arr
|
return arr;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const setOptions = (id, color) => {
|
const setOptions = (id, color) => {
|
||||||
return {
|
return {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: "axis",
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
type: 'shadow',
|
type: "shadow",
|
||||||
shadowStyle: {
|
shadowStyle: {
|
||||||
shadowBlur: 2,
|
shadowBlur: 2,
|
||||||
shadowColor: 'rgba(255, 255, 255, .3)'
|
shadowColor: "rgba(255, 255, 255, .3)",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
left: '3%',
|
left: "3%",
|
||||||
right: '4%',
|
right: "4%",
|
||||||
bottom: '3%',
|
bottom: "3%",
|
||||||
containLabel: true
|
containLabel: true,
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'value',
|
type: "value",
|
||||||
boundaryGap: [0, 0.01]
|
boundaryGap: [0, 0.01],
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'category',
|
type: "category",
|
||||||
data: getPlayerArr(store.state.matchDetails.stats, id, true)
|
data: getPlayerArr(store.state.matchDetails.stats, id, true),
|
||||||
},
|
},
|
||||||
color: color,
|
color: color,
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: 'Enemy',
|
name: "Enemy",
|
||||||
type: 'bar',
|
type: "bar",
|
||||||
data: valueArr(store.state.matchDetails.stats, id, toggle, 'enemy'),
|
data: valueArr(store.state.matchDetails.stats, id, toggle, "enemy"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Team',
|
name: "Team",
|
||||||
type: 'bar',
|
type: "bar",
|
||||||
data: valueArr(store.state.matchDetails.stats, id, toggle, 'team'),
|
data: valueArr(store.state.matchDetails.stats, id, toggle, "team"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Self',
|
name: "Self",
|
||||||
type: 'bar',
|
type: "bar",
|
||||||
data: valueArr(store.state.matchDetails.stats, id, toggle, 'self'),
|
data: valueArr(store.state.matchDetails.stats, id, toggle, "self"),
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const disposeCharts = () => {
|
const disposeCharts = () => {
|
||||||
if (myChart1 != null && myChart1 !== '' && myChart1 !== undefined) {
|
if (myChart1 != null && myChart1 !== "" && myChart1 !== undefined) {
|
||||||
myChart1.dispose()
|
myChart1.dispose();
|
||||||
}
|
}
|
||||||
if (myChart2 != null && myChart2 !== '' && myChart2 !== undefined) {
|
if (myChart2 != null && myChart2 !== "" && myChart2 !== undefined) {
|
||||||
myChart2.dispose()
|
myChart2.dispose();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const buildCharts = () => {
|
const buildCharts = () => {
|
||||||
disposeCharts()
|
disposeCharts();
|
||||||
|
|
||||||
myChart1 = echarts.init(document.getElementById('flash-chart-1'), {}, {
|
myChart1 = echarts.init(
|
||||||
width: width.value,
|
document.getElementById("flash-chart-1"),
|
||||||
height: height.value
|
{},
|
||||||
});
|
{
|
||||||
|
width: width.value,
|
||||||
|
height: height.value,
|
||||||
|
}
|
||||||
|
);
|
||||||
myChart1.setOption(setOptions(1, color));
|
myChart1.setOption(setOptions(1, color));
|
||||||
|
|
||||||
myChart2 = echarts.init(document.getElementById('flash-chart-2'), {}, {
|
myChart2 = echarts.init(
|
||||||
width: width.value,
|
document.getElementById("flash-chart-2"),
|
||||||
height: height.value
|
{},
|
||||||
});
|
{
|
||||||
|
width: width.value,
|
||||||
|
height: height.value,
|
||||||
|
}
|
||||||
|
);
|
||||||
myChart2.setOption(setOptions(2, color));
|
myChart2.setOption(setOptions(2, color));
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (store.state.matchDetails.stats) {
|
if (store.state.matchDetails.stats) {
|
||||||
@@ -152,33 +183,36 @@ export default {
|
|||||||
GridComponent,
|
GridComponent,
|
||||||
LegendComponent,
|
LegendComponent,
|
||||||
BarChart,
|
BarChart,
|
||||||
CanvasRenderer
|
CanvasRenderer,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
buildCharts()
|
buildCharts();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
disposeCharts()
|
disposeCharts();
|
||||||
})
|
});
|
||||||
|
|
||||||
watch(() => toggle.value, () => {
|
watch(
|
||||||
buildCharts()
|
() => toggle.value,
|
||||||
})
|
() => {
|
||||||
|
buildCharts();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
window.onresize = () => {
|
window.onresize = () => {
|
||||||
if (window.innerWidth <= 600) {
|
if (window.innerWidth <= 600) {
|
||||||
width.value = window.innerWidth - 20
|
width.value = window.innerWidth - 20;
|
||||||
height.value = width.value * 2 / 3
|
height.value = (width.value * 2) / 3;
|
||||||
|
|
||||||
buildCharts()
|
buildCharts();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return {toggleShow, toggle}
|
return { toggleShow, toggle };
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -204,7 +238,7 @@ export default {
|
|||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
|
||||||
td {
|
td {
|
||||||
font-size: .8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
td:first-child,
|
td:first-child,
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="footer bg-secondary text-center pt-4 pb-2">
|
|
||||||
<div class="text">
|
|
||||||
<p class="fs-6">Made with <i class="fa fa-heart text-warning" aria-hidden="true"></i>, <span
|
|
||||||
style="color: #41b883">Vue.js</span> and<a aria-label="Gitea" class="text-warning ms-2"
|
|
||||||
href="https://git.harting.dev/CSGOWTF"
|
|
||||||
target="_blank">
|
|
||||||
<i aria-hidden="true" class="fa fa-gitea"></i>
|
|
||||||
</a></p>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-center align-items-center gap-4">
|
|
||||||
<p><a class="text-decoration-none text-warning"
|
|
||||||
href="https://git.harting.dev/CSGOWTF/csgowtf/issues"
|
|
||||||
target="_blank">Issue Tracker</a></p>
|
|
||||||
<p class="text-muted">Version {{ version }}</p>
|
|
||||||
<p>
|
|
||||||
<a class="text-decoration-none text-warning" href="/privacy-policy">Privacy Policy</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: "Footer",
|
|
||||||
setup() {
|
|
||||||
const version = process.env.VUE_APP_VERSION
|
|
||||||
return {version}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.footer {
|
|
||||||
.fa-gitea:hover {
|
|
||||||
color: #609926 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa-heart:hover {
|
|
||||||
color: red !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: .85rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
54
src/components/FooterComponent.vue
Normal file
54
src/components/FooterComponent.vue
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<div class="footer bg-secondary text-center pt-4 pb-2">
|
||||||
|
<div class="text">
|
||||||
|
<p class="fs-6">
|
||||||
|
Made with <i aria-hidden="true" class="fa fa-heart text-warning"></i>,
|
||||||
|
<span style="color: #41b883">Vue.js</span> and<a
|
||||||
|
aria-label="Gitea"
|
||||||
|
class="text-warning ms-2"
|
||||||
|
href="https://git.harting.dev/CSGOWTF"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<i aria-hidden="true" class="fa fa-gitea"></i>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-center align-items-center gap-4">
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
class="text-decoration-none text-warning"
|
||||||
|
href="https://git.harting.dev/CSGOWTF/csgowtf/issues"
|
||||||
|
target="_blank"
|
||||||
|
>Issue Tracker</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p class="text-muted">Version {{ version }}</p>
|
||||||
|
<p>
|
||||||
|
<a class="text-decoration-none text-warning" href="/privacy-policy"
|
||||||
|
>Privacy Policy</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const version = import.meta.env.VITE_VERSION;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.footer {
|
||||||
|
.fa-gitea:hover {
|
||||||
|
color: #609926 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-heart:hover {
|
||||||
|
color: red !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,26 +1,51 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="hitgroup pt-2">
|
<div class="hitgroup pt-2">
|
||||||
<div class="d-flex flex-lg-nowrap flex-wrap justify-content-center gap-4">
|
<div class="d-flex flex-lg-nowrap flex-wrap justify-content-center gap-4">
|
||||||
<div class="d-flex flex-column justify-content-center align-items-center w-auto">
|
<div
|
||||||
|
class="d-flex flex-column justify-content-center align-items-center w-auto"
|
||||||
|
>
|
||||||
<div class="select-group mb-4">
|
<div class="select-group mb-4">
|
||||||
<select v-if="store.state.playersArr" v-model="data.selectPlayer" class="form-select">
|
<select
|
||||||
|
v-if="store.state.playersArr"
|
||||||
|
v-model="data.selectPlayer"
|
||||||
|
class="form-select"
|
||||||
|
>
|
||||||
<option value="All">All</option>
|
<option value="All">All</option>
|
||||||
<option value="Team 1">Team 1</option>
|
<option value="Team 1">Team 1</option>
|
||||||
<option value="Team 2">Team 2</option>
|
<option value="Team 2">Team 2</option>
|
||||||
<option disabled>───────────────────────────</option>
|
<option disabled>───────────────────────────</option>
|
||||||
<option v-for="(value, index) in props.stats" :key="index"
|
<option
|
||||||
:value="Object.keys(value).toString() === store.state.playersArr[index].player.steamid64 ? store.state.playersArr[index].player : ''">
|
v-for="(value, index) in props.stats"
|
||||||
|
:key="index"
|
||||||
|
:value="
|
||||||
|
Object.keys(value).toString() ===
|
||||||
|
store.state.playersArr[index].player.steamid64
|
||||||
|
? store.state.playersArr[index].player
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
>
|
||||||
{{
|
{{
|
||||||
Object.keys(value).toString() === store.state.playersArr[index].player.steamid64 ? store.state.playersArr[index].player.name : ''
|
Object.keys(value).toString() ===
|
||||||
|
store.state.playersArr[index].player.steamid64
|
||||||
|
? store.state.playersArr[index].player.name
|
||||||
|
: ""
|
||||||
}}
|
}}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select v-if="data.selectPlayer !== ''" :key="data.selectPlayer" v-model="data.selectWeapon"
|
<select
|
||||||
class="form-select">
|
v-if="data.selectPlayer !== ''"
|
||||||
|
:key="data.selectPlayer"
|
||||||
|
v-model="data.selectWeapon"
|
||||||
|
class="form-select"
|
||||||
|
>
|
||||||
<option class="select-hr" value="All">All</option>
|
<option class="select-hr" value="All">All</option>
|
||||||
<option disabled>───────────────────────────</option>
|
<option disabled>───────────────────────────</option>
|
||||||
<option v-for="(value, index) in processPlayerWeapon()" :key="index" :value="value">
|
<option
|
||||||
|
v-for="(value, index) in processPlayerWeapon()"
|
||||||
|
:key="index"
|
||||||
|
:value="value"
|
||||||
|
>
|
||||||
<!-- This is here, because weapons are not always named correctly -->
|
<!-- This is here, because weapons are not always named correctly -->
|
||||||
<!-- {{ Object.values(value).toString().charAt(0).toUpperCase() + Object.values(value).toString().slice(1) }}-->
|
<!-- {{ Object.values(value).toString().charAt(0).toUpperCase() + Object.values(value).toString().slice(1) }}-->
|
||||||
{{ Object.values(value).toString() }}
|
{{ Object.values(value).toString() }}
|
||||||
@@ -28,31 +53,59 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="hitgroup-puppet"/>
|
<div id="hitgroup-puppet" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="data.weaponDmg"
|
<div
|
||||||
id="bar-graph"
|
v-if="data.weaponDmg"
|
||||||
class="w-auto"
|
id="bar-graph"
|
||||||
:style="{
|
:style="{
|
||||||
minWidth: dmgWidth + 'px'
|
minWidth: dmgWidth + 'px',
|
||||||
}">
|
}"
|
||||||
|
class="w-auto"
|
||||||
|
>
|
||||||
<table class="table table-borderless">
|
<table class="table table-borderless">
|
||||||
<tr v-for="(value, index) in data.weaponDmg" :key="index">
|
<tr v-for="(value, index) in data.weaponDmg" :key="index">
|
||||||
<td v-if="index < 10 && (data.selectWeapon === 'All' || Object.keys(data.selectWeapon).toString() === Object.keys(value).toString())"
|
<td
|
||||||
style="width: 100px">
|
v-if="
|
||||||
<img :alt="Object.values(value).toString()"
|
index < 10 &&
|
||||||
:src="DisplayWeapon(parseInt(Object.keys(value)[0]))"/>
|
(data.selectWeapon === 'All' ||
|
||||||
|
Object.keys(data.selectWeapon).toString() ===
|
||||||
|
Object.keys(value).toString())
|
||||||
|
"
|
||||||
|
style="width: 100px"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:alt="Object.values(value).toString()"
|
||||||
|
:src="DisplayWeapon(parseInt(Object.keys(value)[0]))"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td v-if="index < 10 && (data.selectWeapon === 'All' || Object.keys(data.selectWeapon).toString() === Object.keys(value).toString())">
|
<td
|
||||||
<span :style="{
|
v-if="
|
||||||
width: (processWeaponDmg(Object.keys(value).toString()) / processWeaponDmg(Object.keys(data.weaponDmg[0]).toString()) * 100).toFixed(0) + '%',
|
index < 10 &&
|
||||||
backgroundColor: 'orangered',
|
(data.selectWeapon === 'All' ||
|
||||||
display: 'block',
|
Object.keys(data.selectWeapon).toString() ===
|
||||||
}"
|
Object.keys(value).toString())
|
||||||
class="rounded"
|
"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:style="{
|
||||||
|
width:
|
||||||
|
(
|
||||||
|
(processWeaponDmg(Object.keys(value).toString()) /
|
||||||
|
processWeaponDmg(
|
||||||
|
Object.keys(data.weaponDmg[0]).toString()
|
||||||
|
)) *
|
||||||
|
100
|
||||||
|
).toFixed(0) + '%',
|
||||||
|
backgroundColor: 'orangered',
|
||||||
|
display: 'block',
|
||||||
|
}"
|
||||||
|
class="rounded"
|
||||||
>
|
>
|
||||||
<span>{{ processWeaponDmg(Object.keys(value).toString()) }}</span>
|
<span>{{
|
||||||
|
processWeaponDmg(Object.keys(value).toString())
|
||||||
|
}}</span>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -63,15 +116,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as echarts from 'echarts/core';
|
import * as echarts from "echarts/core";
|
||||||
import {GeoComponent, TooltipComponent, VisualMapComponent} from 'echarts/components';
|
import {
|
||||||
import {MapChart} from 'echarts/charts';
|
GeoComponent,
|
||||||
import {CanvasRenderer} from 'echarts/renderers';
|
TooltipComponent,
|
||||||
import {onMounted, onUnmounted, reactive, ref, watch} from "vue";
|
VisualMapComponent,
|
||||||
import {useStore} from "vuex";
|
} from "echarts/components";
|
||||||
import {DisplayWeapon} from '@/utils'
|
import { MapChart } from "echarts/charts";
|
||||||
|
import { CanvasRenderer } from "echarts/renderers";
|
||||||
|
import { onMounted, onUnmounted, reactive, ref, watch } from "vue";
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
import { DisplayWeapon } from "/src/utils";
|
||||||
|
|
||||||
import $ from 'jquery'
|
import $ from "jquery";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "HitgroupPuppet.vue",
|
name: "HitgroupPuppet.vue",
|
||||||
@@ -82,413 +139,460 @@ export default {
|
|||||||
},
|
},
|
||||||
stats: {
|
stats: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const store = useStore()
|
const store = useStore();
|
||||||
|
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
selectPlayer: 'All',
|
selectPlayer: "All",
|
||||||
selectWeapon: 'All',
|
selectWeapon: "All",
|
||||||
eq_map: [],
|
eq_map: [],
|
||||||
weaponDmg: []
|
weaponDmg: [],
|
||||||
})
|
});
|
||||||
|
|
||||||
let myChart1
|
let myChart1;
|
||||||
|
|
||||||
const getWindowWidth = () => {
|
const getWindowWidth = () => {
|
||||||
const windowWidth = window.innerWidth
|
const windowWidth = window.innerWidth;
|
||||||
if (windowWidth <= 750)
|
if (windowWidth <= 750) return windowWidth;
|
||||||
return windowWidth
|
else return 650;
|
||||||
else
|
};
|
||||||
return 650
|
|
||||||
}
|
|
||||||
|
|
||||||
const setDmgWidth = () => {
|
const setDmgWidth = () => {
|
||||||
const windowWidth = getWindowWidth()
|
const windowWidth = getWindowWidth();
|
||||||
if (windowWidth >= 500)
|
if (windowWidth >= 500) return 500;
|
||||||
return 500
|
else return windowWidth - 10;
|
||||||
else
|
};
|
||||||
return windowWidth - 10
|
|
||||||
}
|
|
||||||
|
|
||||||
const dmgWidth = ref(setDmgWidth())
|
const dmgWidth = ref(setDmgWidth());
|
||||||
|
|
||||||
const setHeight = () => {
|
const setHeight = () => {
|
||||||
const windowWidth = getWindowWidth()
|
const windowWidth = getWindowWidth();
|
||||||
if (windowWidth >= 751)
|
if (windowWidth >= 751) return (windowWidth * 3) / 7.5;
|
||||||
return windowWidth * 3 / 7.5
|
|
||||||
else if (windowWidth >= 501 && windowWidth <= 750)
|
else if (windowWidth >= 501 && windowWidth <= 750)
|
||||||
return windowWidth * 3 / 6.5
|
return (windowWidth * 3) / 6.5;
|
||||||
else
|
else return (windowWidth * 3) / 5.5;
|
||||||
return windowWidth * 3 / 5.5
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const width = ref(getWindowWidth())
|
const width = ref(getWindowWidth());
|
||||||
const height = ref(setHeight())
|
const height = ref(setHeight());
|
||||||
|
|
||||||
const processWeaponDmg = (id) => {
|
const processWeaponDmg = (id) => {
|
||||||
let value = ''
|
let value = "";
|
||||||
data.weaponDmg.forEach(w => {
|
data.weaponDmg.forEach((w) => {
|
||||||
if (Object.keys(w).toString() === id) {
|
if (Object.keys(w).toString() === id) {
|
||||||
value = Object.values(w).toString()
|
value = Object.values(w).toString();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
return value
|
return value;
|
||||||
}
|
};
|
||||||
|
|
||||||
const processPlayerWeapon = () => {
|
const processPlayerWeapon = () => {
|
||||||
let arr = []
|
let arr = [];
|
||||||
if (data.selectPlayer === 'All') {
|
if (data.selectPlayer === "All") {
|
||||||
props.stats.forEach(player => {
|
props.stats.forEach((player) => {
|
||||||
Object.values(player).forEach(enemies => {
|
Object.values(player).forEach((enemies) => {
|
||||||
Object.values(enemies).forEach(weapons => {
|
Object.values(enemies).forEach((weapons) => {
|
||||||
Object.values(weapons).forEach(weapon => {
|
Object.values(weapons).forEach((weapon) => {
|
||||||
arr.push(weapon[0])
|
arr.push(weapon[0]);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
} else if (data.selectPlayer === 'Team 1') {
|
} else if (data.selectPlayer === "Team 1") {
|
||||||
props.stats.forEach(player => {
|
props.stats.forEach((player) => {
|
||||||
store.state.playersArr.forEach(p => {
|
store.state.playersArr.forEach((p) => {
|
||||||
if (p.player.steamid64 === Object.keys(player).toString() && p.team_id === 1)
|
if (
|
||||||
Object.values(player).forEach(enemies => {
|
p.player.steamid64 === Object.keys(player).toString() &&
|
||||||
Object.values(enemies).forEach(weapons => {
|
p.team_id === 1
|
||||||
Object.values(weapons).forEach(weapon => {
|
)
|
||||||
arr.push(weapon[0])
|
Object.values(player).forEach((enemies) => {
|
||||||
})
|
Object.values(enemies).forEach((weapons) => {
|
||||||
})
|
Object.values(weapons).forEach((weapon) => {
|
||||||
})
|
arr.push(weapon[0]);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
} else if (data.selectPlayer === 'Team 2') {
|
});
|
||||||
props.stats.forEach(player => {
|
});
|
||||||
store.state.playersArr.forEach(p => {
|
});
|
||||||
if (p.player.steamid64 === Object.keys(player).toString() && p.team_id === 2)
|
} else if (data.selectPlayer === "Team 2") {
|
||||||
Object.values(player).forEach(enemies => {
|
props.stats.forEach((player) => {
|
||||||
Object.values(enemies).forEach(weapons => {
|
store.state.playersArr.forEach((p) => {
|
||||||
Object.values(weapons).forEach(weapon => {
|
if (
|
||||||
arr.push(weapon[0])
|
p.player.steamid64 === Object.keys(player).toString() &&
|
||||||
})
|
p.team_id === 2
|
||||||
})
|
)
|
||||||
})
|
Object.values(player).forEach((enemies) => {
|
||||||
})
|
Object.values(enemies).forEach((weapons) => {
|
||||||
})
|
Object.values(weapons).forEach((weapon) => {
|
||||||
|
arr.push(weapon[0]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
props.stats.forEach(player => {
|
props.stats.forEach((player) => {
|
||||||
if (Object.keys(player).toString() === data.selectPlayer.steamid64) {
|
if (Object.keys(player).toString() === data.selectPlayer.steamid64) {
|
||||||
Object.values(player).forEach(enemies => {
|
Object.values(player).forEach((enemies) => {
|
||||||
Object.values(enemies).forEach(weapons => {
|
Object.values(enemies).forEach((weapons) => {
|
||||||
Object.values(weapons).forEach(weapon => {
|
Object.values(weapons).forEach((weapon) => {
|
||||||
arr.push(weapon[0])
|
arr.push(weapon[0]);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const unique = arr.filter((a, b) => arr.indexOf(a) === b && a < 400)
|
const unique = arr.filter((a, b) => arr.indexOf(a) === b && a < 400);
|
||||||
|
|
||||||
let arr2 = []
|
let arr2 = [];
|
||||||
|
|
||||||
unique.forEach(w => {
|
unique.forEach((w) => {
|
||||||
for (let weapon in props.equipment_map) {
|
for (let weapon in props.equipment_map) {
|
||||||
if (parseInt(w) === parseInt(weapon)) {
|
if (parseInt(w) === parseInt(weapon)) {
|
||||||
let obj = {}
|
let obj = {};
|
||||||
obj[w] = props.equipment_map[weapon]
|
obj[w] = props.equipment_map[weapon];
|
||||||
arr2.push(obj)
|
arr2.push(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
return arr2
|
return arr2;
|
||||||
}
|
};
|
||||||
|
|
||||||
const processDmg = (by = 'hitgroup') => {
|
const processDmg = (by = "hitgroup") => {
|
||||||
let arr = []
|
let arr = [];
|
||||||
if (data.selectPlayer && data.selectWeapon) {
|
if (data.selectPlayer && data.selectWeapon) {
|
||||||
switch (data.selectPlayer) {
|
switch (data.selectPlayer) {
|
||||||
case "All":
|
case "All":
|
||||||
props.stats.forEach(player => {
|
props.stats.forEach((player) => {
|
||||||
Object.values(player).forEach(enemies => {
|
Object.values(player).forEach((enemies) => {
|
||||||
Object.values(enemies).forEach(weapons => {
|
Object.values(enemies).forEach((weapons) => {
|
||||||
Object.values(weapons).forEach(weapon => {
|
Object.values(weapons).forEach((weapon) => {
|
||||||
// 0: weapon
|
// 0: weapon
|
||||||
// 1: hitgroup
|
// 1: hitgroup
|
||||||
// 2: dmg
|
// 2: dmg
|
||||||
if (weapon) {
|
if (weapon) {
|
||||||
if (by === 'hitgroup') {
|
if (by === "hitgroup") {
|
||||||
if (Object.values(weapon)[0] === parseInt(Object.keys(data.selectWeapon).toString())) {
|
if (
|
||||||
let obj = {}
|
Object.values(weapon)[0] ===
|
||||||
obj[weapon[1]] = weapon[2]
|
parseInt(Object.keys(data.selectWeapon).toString())
|
||||||
arr.push(obj)
|
) {
|
||||||
} else if (data.selectWeapon === 'All') {
|
let obj = {};
|
||||||
let obj = {}
|
obj[weapon[1]] = weapon[2];
|
||||||
obj[weapon[1]] = weapon[2]
|
arr.push(obj);
|
||||||
arr.push(obj)
|
} else if (data.selectWeapon === "All") {
|
||||||
|
let obj = {};
|
||||||
|
obj[weapon[1]] = weapon[2];
|
||||||
|
arr.push(obj);
|
||||||
}
|
}
|
||||||
} else if (by === 'weapon') {
|
} else if (by === "weapon") {
|
||||||
if (Object.values(weapon)[0] === parseInt(Object.keys(data.selectWeapon).toString())) {
|
if (
|
||||||
let obj = {}
|
Object.values(weapon)[0] ===
|
||||||
obj[weapon[0]] = weapon[2]
|
parseInt(Object.keys(data.selectWeapon).toString())
|
||||||
arr.push(obj)
|
) {
|
||||||
} else if (data.selectWeapon === 'All') {
|
let obj = {};
|
||||||
let obj = {}
|
obj[weapon[0]] = weapon[2];
|
||||||
obj[weapon[0]] = weapon[2]
|
arr.push(obj);
|
||||||
arr.push(obj)
|
} else if (data.selectWeapon === "All") {
|
||||||
|
let obj = {};
|
||||||
|
obj[weapon[0]] = weapon[2];
|
||||||
|
arr.push(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "Team 1":
|
case "Team 1":
|
||||||
props.stats.forEach(player => {
|
props.stats.forEach((player) => {
|
||||||
store.state.playersArr.forEach(p => {
|
store.state.playersArr.forEach((p) => {
|
||||||
if (p.player.steamid64 === Object.keys(player).toString() && p.team_id === 1)
|
if (
|
||||||
Object.values(player).forEach(enemies => {
|
p.player.steamid64 === Object.keys(player).toString() &&
|
||||||
Object.values(enemies).forEach(weapons => {
|
p.team_id === 1
|
||||||
Object.values(weapons).forEach(weapon => {
|
)
|
||||||
|
Object.values(player).forEach((enemies) => {
|
||||||
|
Object.values(enemies).forEach((weapons) => {
|
||||||
|
Object.values(weapons).forEach((weapon) => {
|
||||||
// 0: weapon
|
// 0: weapon
|
||||||
// 1: hitgroup
|
// 1: hitgroup
|
||||||
// 2: dmg
|
// 2: dmg
|
||||||
if (weapon) {
|
if (weapon) {
|
||||||
if (by === 'hitgroup') {
|
if (by === "hitgroup") {
|
||||||
if (Object.values(weapon)[0] === parseInt(Object.keys(data.selectWeapon).toString())) {
|
if (
|
||||||
let obj = {}
|
Object.values(weapon)[0] ===
|
||||||
obj[weapon[1]] = weapon[2]
|
parseInt(
|
||||||
arr.push(obj)
|
Object.keys(data.selectWeapon).toString()
|
||||||
} else if (data.selectWeapon === 'All') {
|
)
|
||||||
let obj = {}
|
) {
|
||||||
obj[weapon[1]] = weapon[2]
|
let obj = {};
|
||||||
arr.push(obj)
|
obj[weapon[1]] = weapon[2];
|
||||||
|
arr.push(obj);
|
||||||
|
} else if (data.selectWeapon === "All") {
|
||||||
|
let obj = {};
|
||||||
|
obj[weapon[1]] = weapon[2];
|
||||||
|
arr.push(obj);
|
||||||
}
|
}
|
||||||
} else if (by === 'weapon') {
|
} else if (by === "weapon") {
|
||||||
if (Object.values(weapon)[0] === parseInt(Object.keys(data.selectWeapon).toString())) {
|
if (
|
||||||
let obj = {}
|
Object.values(weapon)[0] ===
|
||||||
obj[weapon[0]] = weapon[2]
|
parseInt(
|
||||||
arr.push(obj)
|
Object.keys(data.selectWeapon).toString()
|
||||||
} else if (data.selectWeapon === 'All') {
|
)
|
||||||
let obj = {}
|
) {
|
||||||
obj[weapon[0]] = weapon[2]
|
let obj = {};
|
||||||
arr.push(obj)
|
obj[weapon[0]] = weapon[2];
|
||||||
|
arr.push(obj);
|
||||||
|
} else if (data.selectWeapon === "All") {
|
||||||
|
let obj = {};
|
||||||
|
obj[weapon[0]] = weapon[2];
|
||||||
|
arr.push(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "Team 2":
|
case "Team 2":
|
||||||
props.stats.forEach(player => {
|
props.stats.forEach((player) => {
|
||||||
store.state.playersArr.forEach(p => {
|
store.state.playersArr.forEach((p) => {
|
||||||
if (p.player.steamid64 === Object.keys(player).toString() && p.team_id === 2)
|
if (
|
||||||
Object.values(player).forEach(enemies => {
|
p.player.steamid64 === Object.keys(player).toString() &&
|
||||||
Object.values(enemies).forEach(weapons => {
|
p.team_id === 2
|
||||||
Object.values(weapons).forEach(weapon => {
|
)
|
||||||
|
Object.values(player).forEach((enemies) => {
|
||||||
|
Object.values(enemies).forEach((weapons) => {
|
||||||
|
Object.values(weapons).forEach((weapon) => {
|
||||||
// 0: weapon
|
// 0: weapon
|
||||||
// 1: hitgroup
|
// 1: hitgroup
|
||||||
// 2: dmg
|
// 2: dmg
|
||||||
if (weapon) {
|
if (weapon) {
|
||||||
if (by === 'hitgroup') {
|
if (by === "hitgroup") {
|
||||||
if (Object.values(weapon)[0] === parseInt(Object.keys(data.selectWeapon).toString())) {
|
if (
|
||||||
let obj = {}
|
Object.values(weapon)[0] ===
|
||||||
obj[weapon[1]] = weapon[2]
|
parseInt(
|
||||||
arr.push(obj)
|
Object.keys(data.selectWeapon).toString()
|
||||||
} else if (data.selectWeapon === 'All') {
|
)
|
||||||
let obj = {}
|
) {
|
||||||
obj[weapon[1]] = weapon[2]
|
let obj = {};
|
||||||
arr.push(obj)
|
obj[weapon[1]] = weapon[2];
|
||||||
|
arr.push(obj);
|
||||||
|
} else if (data.selectWeapon === "All") {
|
||||||
|
let obj = {};
|
||||||
|
obj[weapon[1]] = weapon[2];
|
||||||
|
arr.push(obj);
|
||||||
}
|
}
|
||||||
} else if (by === 'weapon') {
|
} else if (by === "weapon") {
|
||||||
if (Object.values(weapon)[0] === parseInt(Object.keys(data.selectWeapon).toString())) {
|
if (
|
||||||
let obj = {}
|
Object.values(weapon)[0] ===
|
||||||
obj[weapon[0]] = weapon[2]
|
parseInt(
|
||||||
arr.push(obj)
|
Object.keys(data.selectWeapon).toString()
|
||||||
} else if (data.selectWeapon === 'All') {
|
)
|
||||||
let obj = {}
|
) {
|
||||||
obj[weapon[0]] = weapon[2]
|
let obj = {};
|
||||||
arr.push(obj)
|
obj[weapon[0]] = weapon[2];
|
||||||
|
arr.push(obj);
|
||||||
|
} else if (data.selectWeapon === "All") {
|
||||||
|
let obj = {};
|
||||||
|
obj[weapon[0]] = weapon[2];
|
||||||
|
arr.push(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
props.stats.forEach(player => {
|
props.stats.forEach((player) => {
|
||||||
if (Object.keys(player).toString() === data.selectPlayer.steamid64) {
|
if (
|
||||||
Object.values(player).forEach(enemies => {
|
Object.keys(player).toString() === data.selectPlayer.steamid64
|
||||||
Object.values(enemies).forEach(weapons => {
|
) {
|
||||||
Object.values(weapons).forEach(weapon => {
|
Object.values(player).forEach((enemies) => {
|
||||||
|
Object.values(enemies).forEach((weapons) => {
|
||||||
|
Object.values(weapons).forEach((weapon) => {
|
||||||
// 0: weapon
|
// 0: weapon
|
||||||
// 1: hitgroup
|
// 1: hitgroup
|
||||||
// 2: dmg
|
// 2: dmg
|
||||||
if (weapon) {
|
if (weapon) {
|
||||||
if (by === 'hitgroup') {
|
if (by === "hitgroup") {
|
||||||
if (Object.values(weapon)[0] === parseInt(Object.keys(data.selectWeapon).toString())) {
|
if (
|
||||||
let obj = {}
|
Object.values(weapon)[0] ===
|
||||||
obj[weapon[1]] = weapon[2]
|
parseInt(Object.keys(data.selectWeapon).toString())
|
||||||
arr.push(obj)
|
) {
|
||||||
} else if (data.selectWeapon === 'All') {
|
let obj = {};
|
||||||
let obj = {}
|
obj[weapon[1]] = weapon[2];
|
||||||
obj[weapon[1]] = weapon[2]
|
arr.push(obj);
|
||||||
arr.push(obj)
|
} else if (data.selectWeapon === "All") {
|
||||||
|
let obj = {};
|
||||||
|
obj[weapon[1]] = weapon[2];
|
||||||
|
arr.push(obj);
|
||||||
}
|
}
|
||||||
} else if (by === 'weapon') {
|
} else if (by === "weapon") {
|
||||||
if (Object.values(weapon)[0] === parseInt(Object.keys(data.selectWeapon).toString())) {
|
if (
|
||||||
let obj = {}
|
Object.values(weapon)[0] ===
|
||||||
obj[weapon[0]] = weapon[2]
|
parseInt(Object.keys(data.selectWeapon).toString())
|
||||||
arr.push(obj)
|
) {
|
||||||
} else if (data.selectWeapon === 'All') {
|
let obj = {};
|
||||||
let obj = {}
|
obj[weapon[0]] = weapon[2];
|
||||||
obj[weapon[0]] = weapon[2]
|
arr.push(obj);
|
||||||
arr.push(obj)
|
} else if (data.selectWeapon === "All") {
|
||||||
|
let obj = {};
|
||||||
|
obj[weapon[0]] = weapon[2];
|
||||||
|
arr.push(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
arr = []
|
arr = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (by === 'hitgroup') {
|
if (by === "hitgroup") {
|
||||||
buildCharts(sumDmgArr(arr))
|
buildCharts(sumDmgArr(arr));
|
||||||
} else if (by === 'weapon') {
|
} else if (by === "weapon") {
|
||||||
data.weaponDmg = sumDmgArr(arr, 'weapon')
|
data.weaponDmg = sumDmgArr(arr, "weapon");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const sumDmgArr = (arr, by = 'hitgroup') => {
|
const sumDmgArr = (arr, by = "hitgroup") => {
|
||||||
let holder = {};
|
let holder = {};
|
||||||
|
|
||||||
arr.forEach(function (d) {
|
arr.forEach(function (d) {
|
||||||
// eslint-disable-next-line no-prototype-builtins
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
if (holder.hasOwnProperty(parseInt(Object.keys(d).toString()))) {
|
if (holder.hasOwnProperty(parseInt(Object.keys(d).toString()))) {
|
||||||
holder[parseInt(Object.keys(d).toString())] = holder[parseInt(Object.keys(d).toString())] + parseInt(Object.values(d).toString());
|
holder[parseInt(Object.keys(d).toString())] =
|
||||||
|
holder[parseInt(Object.keys(d).toString())] +
|
||||||
|
parseInt(Object.values(d).toString());
|
||||||
} else {
|
} else {
|
||||||
holder[parseInt(Object.keys(d).toString())] = parseInt(Object.values(d).toString());
|
holder[parseInt(Object.keys(d).toString())] = parseInt(
|
||||||
|
Object.values(d).toString()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let arr2 = [];
|
let arr2 = [];
|
||||||
|
|
||||||
if (by === 'hitgroup') {
|
if (by === "hitgroup") {
|
||||||
for (let i = 1; i < 8; i++) {
|
for (let i = 1; i < 8; i++) {
|
||||||
if (holder[i] !== undefined) {
|
if (holder[i] !== undefined) {
|
||||||
arr2.push(holder[i])
|
arr2.push(holder[i]);
|
||||||
} else {
|
} else {
|
||||||
arr2.push(0)
|
arr2.push(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (by === 'weapon') {
|
} else if (by === "weapon") {
|
||||||
for (let i = 1; i < 312; i++) {
|
for (let i = 1; i < 312; i++) {
|
||||||
if (holder[i] !== undefined) {
|
if (holder[i] !== undefined) {
|
||||||
let obj = {}
|
let obj = {};
|
||||||
obj[i] = holder[i]
|
obj[i] = holder[i];
|
||||||
arr2.push(obj)
|
arr2.push(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
arr2.sort((a, b) => {
|
arr2.sort((a, b) => {
|
||||||
return Object.values(b).toString() - Object.values(a).toString()
|
return Object.values(b).toString() - Object.values(a).toString();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return arr2
|
return arr2;
|
||||||
}
|
};
|
||||||
|
|
||||||
const getMax = (arr) => {
|
const getMax = (arr) => {
|
||||||
let max = 0
|
let max = 0;
|
||||||
for (let i = 0; i < 7; i++) {
|
for (let i = 0; i < 7; i++) {
|
||||||
if (arr[i] > max)
|
if (arr[i] > max) max = arr[i];
|
||||||
max = arr[i]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return max
|
return max;
|
||||||
}
|
};
|
||||||
|
|
||||||
const optionGen = (arr = []) => {
|
const optionGen = (arr = []) => {
|
||||||
return {
|
return {
|
||||||
tooltip: {},
|
tooltip: {},
|
||||||
visualMap: {
|
visualMap: {
|
||||||
left: 'center',
|
left: "center",
|
||||||
bottom: '5%',
|
bottom: "5%",
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: 'white',
|
color: "white",
|
||||||
},
|
},
|
||||||
min: 0,
|
min: 0,
|
||||||
max: getMax(arr) || 100,
|
max: getMax(arr) || 100,
|
||||||
orient: 'horizontal',
|
orient: "horizontal",
|
||||||
realtime: true,
|
realtime: true,
|
||||||
calculable: true,
|
calculable: true,
|
||||||
inRange: {
|
inRange: {
|
||||||
color: ['#00ff00', '#db6e00', '#cf0000']
|
color: ["#00ff00", "#db6e00", "#cf0000"],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: 'Hitgroup',
|
name: "Hitgroup",
|
||||||
type: 'map',
|
type: "map",
|
||||||
map: 'hitgroup-puppet',
|
map: "hitgroup-puppet",
|
||||||
top: '0%',
|
top: "0%",
|
||||||
emphasis: {
|
emphasis: {
|
||||||
label: {
|
label: {
|
||||||
show: false
|
show: false,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
selectedMode: false,
|
selectedMode: false,
|
||||||
data: [
|
data: [
|
||||||
{name: 'Head', value: arr[0] || 0},
|
{ name: "Head", value: arr[0] || 0 },
|
||||||
{name: 'Chest', value: arr[1] || 0},
|
{ name: "Chest", value: arr[1] || 0 },
|
||||||
{name: 'Stomach', value: arr[2] || 0},
|
{ name: "Stomach", value: arr[2] || 0 },
|
||||||
{name: 'Left Arm', value: arr[3] || 0},
|
{ name: "Left Arm", value: arr[3] || 0 },
|
||||||
{name: 'Right Arm', value: arr[4] || 0},
|
{ name: "Right Arm", value: arr[4] || 0 },
|
||||||
{name: 'Left Foot', value: arr[5] || 0},
|
{ name: "Left Foot", value: arr[5] || 0 },
|
||||||
{name: 'Right Foot', value: arr[6] || 0}
|
{ name: "Right Foot", value: arr[6] || 0 },
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const disposeCharts = () => {
|
const disposeCharts = () => {
|
||||||
if (myChart1 != null && myChart1 !== '' && myChart1 !== undefined) {
|
if (myChart1 != null && myChart1 !== "" && myChart1 !== undefined) {
|
||||||
myChart1.dispose()
|
myChart1.dispose();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const buildCharts = (arr) => {
|
const buildCharts = (arr) => {
|
||||||
disposeCharts()
|
disposeCharts();
|
||||||
|
|
||||||
myChart1 = echarts.init(document.getElementById('hitgroup-puppet'), {}, {width: 300, height: 500})
|
myChart1 = echarts.init(
|
||||||
|
document.getElementById("hitgroup-puppet"),
|
||||||
|
{},
|
||||||
|
{ width: 300, height: 500 }
|
||||||
|
);
|
||||||
|
|
||||||
const url = '/images/icons/hitgroup-puppet.svg'
|
const url = "/images/icons/hitgroup-puppet.svg";
|
||||||
$.get(url, function (svg) {
|
$.get(url, function (svg) {
|
||||||
echarts.registerMap('hitgroup-puppet', {svg: svg})
|
echarts.registerMap("hitgroup-puppet", { svg: svg });
|
||||||
myChart1.setOption(optionGen(arr));
|
myChart1.setOption(optionGen(arr));
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (store.state.matchDetails.stats) {
|
if (store.state.matchDetails.stats) {
|
||||||
@@ -497,48 +601,65 @@ export default {
|
|||||||
VisualMapComponent,
|
VisualMapComponent,
|
||||||
GeoComponent,
|
GeoComponent,
|
||||||
MapChart,
|
MapChart,
|
||||||
CanvasRenderer
|
CanvasRenderer,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
buildCharts()
|
buildCharts();
|
||||||
|
|
||||||
watch(() => props.stats, () => {
|
watch(
|
||||||
processDmg()
|
() => props.stats,
|
||||||
processDmg('weapon')
|
() => {
|
||||||
processPlayerWeapon()
|
processDmg();
|
||||||
})
|
processDmg("weapon");
|
||||||
|
processPlayerWeapon();
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
disposeCharts()
|
disposeCharts();
|
||||||
})
|
});
|
||||||
|
|
||||||
window.onresize = () => {
|
window.onresize = () => {
|
||||||
if (window.innerWidth <= 750) {
|
if (window.innerWidth <= 750) {
|
||||||
width.value = getWindowWidth() - 20
|
width.value = getWindowWidth() - 20;
|
||||||
height.value = setHeight()
|
height.value = setHeight();
|
||||||
dmgWidth.value = setDmgWidth()
|
dmgWidth.value = setDmgWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
buildCharts()
|
buildCharts();
|
||||||
}
|
};
|
||||||
|
|
||||||
watch(() => data.selectPlayer, () => {
|
watch(
|
||||||
data.selectWeapon = 'All'
|
() => data.selectPlayer,
|
||||||
processPlayerWeapon()
|
() => {
|
||||||
processDmg()
|
data.selectWeapon = "All";
|
||||||
processDmg('weapon')
|
processPlayerWeapon();
|
||||||
})
|
processDmg();
|
||||||
|
processDmg("weapon");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
watch(() => data.selectWeapon, () => {
|
watch(
|
||||||
processDmg()
|
() => data.selectWeapon,
|
||||||
processDmg('weapon')
|
() => {
|
||||||
})
|
processDmg();
|
||||||
|
processDmg("weapon");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return {props, data, store, dmgWidth, processPlayerWeapon, processWeaponDmg, DisplayWeapon}
|
return {
|
||||||
}
|
props,
|
||||||
}
|
data,
|
||||||
|
store,
|
||||||
|
dmgWidth,
|
||||||
|
processPlayerWeapon,
|
||||||
|
processWeaponDmg,
|
||||||
|
DisplayWeapon,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -1,54 +1,61 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="infos.data" id="modal">
|
<div v-if="infos.data" id="modal">
|
||||||
<div v-for="(info, id) in infos.data" :key="id" class="custom-modal">
|
<div v-for="(info, id) in infos.data" :key="id" class="custom-modal">
|
||||||
<div :class="info.type === 'error'
|
<div
|
||||||
? 'bg-danger text-white'
|
:class="
|
||||||
: info.type === 'warning'
|
info.type === 'error'
|
||||||
? 'bg-warning text-secondary'
|
? 'bg-danger text-white'
|
||||||
: info.type === 'success'
|
: info.type === 'warning'
|
||||||
? 'bg-success text-white'
|
? 'bg-warning text-secondary'
|
||||||
: 'bg-secondary text-white'"
|
: info.type === 'success'
|
||||||
class="card">
|
? 'bg-success text-white'
|
||||||
|
: 'bg-secondary text-white'
|
||||||
|
"
|
||||||
|
class="card"
|
||||||
|
>
|
||||||
<div class="card-body d-flex justify-content-between">
|
<div class="card-body d-flex justify-content-between">
|
||||||
<span class="info-text">{{ info.message }}</span>
|
<span class="info-text">{{ info.message }}</span>
|
||||||
<button aria-label="Close" class="btn-close" type="button" @click="closeModal(id)"/>
|
<button
|
||||||
|
aria-label="Close"
|
||||||
|
class="btn-close"
|
||||||
|
type="button"
|
||||||
|
@click="closeModal(id)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import {useStore} from "vuex";
|
import { onMounted, reactive } from "vue";
|
||||||
import {onMounted, reactive} from "vue";
|
import type { infoState } from "@/stores/infoState";
|
||||||
|
import { useInfoStateStore } from "@/stores/infoState";
|
||||||
|
|
||||||
export default {
|
const infoStateStore = useInfoStateStore();
|
||||||
name: "InfoModal",
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
const infos = reactive({
|
|
||||||
data: []
|
|
||||||
})
|
|
||||||
|
|
||||||
const closeModal = (id) => {
|
const infos = reactive({
|
||||||
store.commit('removeInfoState', id)
|
data: [] as infoState[],
|
||||||
|
count: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeModal = (id: number) => {
|
||||||
|
infoStateStore.removeInfo(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
infos.count = infoStateStore.infoCount;
|
||||||
|
|
||||||
|
infoStateStore.$subscribe((mutation, state) => {
|
||||||
|
if (state.infoCount !== infos.count) {
|
||||||
|
infos.data = state.infoState;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
closeModal(infoStateStore.infoState.length - 1);
|
||||||
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
onMounted(() => {
|
});
|
||||||
store.subscribe(((mutation, state) => {
|
|
||||||
if (mutation.type === 'changeInfoState') {
|
|
||||||
infos.data = state.info
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
closeModal(store.state.info.length - 1)
|
|
||||||
}, 5000)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
return {infos, closeModal}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -59,17 +66,17 @@ export default {
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 1rem;
|
right: 1rem;
|
||||||
opacity: .8;
|
opacity: 0.8;
|
||||||
width: min(100vw - 2rem, 50ch);
|
width: min(100vw - 2rem, 50ch);
|
||||||
height: var(--height);
|
height: var(--height);
|
||||||
|
|
||||||
.btn-close {
|
.btn-close {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
opacity: .5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-text {
|
.info-text {
|
||||||
font-size: .8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,56 +2,91 @@
|
|||||||
<div class="container w-50">
|
<div class="container w-50">
|
||||||
<TranslateChatButton
|
<TranslateChatButton
|
||||||
v-if="data.chat.length > 0"
|
v-if="data.chat.length > 0"
|
||||||
:translated="data.translatedText.length > 0"
|
|
||||||
class="translate-btn"
|
class="translate-btn"
|
||||||
@translated="handleTranslatedText"
|
@translated="handleTranslatedText"
|
||||||
/>
|
/>
|
||||||
<div v-if="data.chat.length > 0" class="chat-history mt-2">
|
<div v-if="data.chat.length > 0" class="chat-history mt-2">
|
||||||
<table id="chat" :style="`max-width: ${data.clientWidth}px; width: ${data.clientWidth}px`" class="table table-borderless">
|
<table
|
||||||
|
id="chat"
|
||||||
|
:style="`max-width: ${data.clientWidth}px; width: ${data.clientWidth}px`"
|
||||||
|
class="table table-borderless"
|
||||||
|
>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(m, id) in data.chat" :key="id">
|
<tr v-for="(m, id) in data.chat" :key="id">
|
||||||
<td class="td-time">
|
<td class="td-time">
|
||||||
{{ ConvertTickToTime(m.tick, m.tick_rate) }}
|
{{ ConvertTickToTime(m.tick, m.tick_rate) }}
|
||||||
</td>
|
</td>
|
||||||
<td class="td-avatar">
|
<td class="td-avatar">
|
||||||
<img :class="'team-color-' + m.color"
|
<img
|
||||||
:src="constructAvatarUrl(m.avatar)"
|
:class="'team-color-' + m.color"
|
||||||
alt="Player avatar"
|
:src="constructAvatarUrl(m.avatar)"
|
||||||
class="avatar">
|
alt="Player avatar"
|
||||||
|
class="avatar"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td :class="m.startSide === 1 ? 'text-info' : 'text-warning'"
|
<td
|
||||||
|
:class="m.startSide === 1 ? 'text-info' : 'text-warning'"
|
||||||
class="td-name d-flex"
|
class="td-name d-flex"
|
||||||
@click="GoToPlayer(m.steamid64)">
|
@click="GoToPlayer(m.steamid64)"
|
||||||
<span>
|
>
|
||||||
<i v-if="m.tracked" class="fa fa-dot-circle-o text-success tracked" title="Tracked user"/>
|
<span>
|
||||||
<span :class="(m.vac && FormatVacDate(m.vac_date, store.state.matchDetails.date) !== '')
|
<i
|
||||||
|| (!m.vac && m.game_ban && FormatVacDate(m.game_ban_date, store.state.matchDetails.date) !== '')
|
v-if="m.tracked"
|
||||||
? 'ban-shadow'
|
class="fa fa-dot-circle-o text-success tracked"
|
||||||
: ''"
|
title="Tracked user"
|
||||||
:title="!m.vac && m.game_ban
|
/>
|
||||||
? 'Game-banned: ' + FormatVacDate(m.game_ban_date, store.state.matchDetails.date)
|
<span
|
||||||
: m.vac && !m.game_ban
|
:class="
|
||||||
? 'Vac-banned: ' + FormatVacDate(m.vac_date, store.state.matchDetails.date)
|
(m.vac &&
|
||||||
: ''">
|
FormatVacDate(
|
||||||
{{ m.player }}
|
m.vac_date,
|
||||||
</span>
|
matchDetailsStore.matchDetails.date
|
||||||
</span>
|
) !== '') ||
|
||||||
|
(!m.vac &&
|
||||||
|
m.game_ban &&
|
||||||
|
FormatVacDate(
|
||||||
|
m.game_ban_date,
|
||||||
|
matchDetailsStore.matchDetails.date
|
||||||
|
) !== '')
|
||||||
|
? 'ban-shadow'
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
:title="
|
||||||
|
!m.vac && m.game_ban
|
||||||
|
? 'Game-banned: ' +
|
||||||
|
FormatVacDate(
|
||||||
|
m.game_ban_date,
|
||||||
|
matchDetailsStore.matchDetails.date
|
||||||
|
)
|
||||||
|
: m.vac && !m.game_ban
|
||||||
|
? 'Vac-banned: ' +
|
||||||
|
FormatVacDate(m.vac_date, matchDetailsStore.matchDetails.date)
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ m.player }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="td-icon">
|
<td class="td-icon">
|
||||||
<i class="fa fa-caret-right"/>
|
<i class="fa fa-caret-right"/>
|
||||||
<span v-if="!m.all_chat" class="ms-1">
|
<span v-if="!m.all_chat" class="ms-1"> (team) </span>
|
||||||
(team)
|
|
||||||
</span>
|
|
||||||
</td>
|
</td>
|
||||||
<td class="td-message">
|
<td class="td-message">
|
||||||
{{ data.translatedText.length === 0 ? m.message : data.originalChat[id].message }}
|
{{
|
||||||
<span v-if="m.translated_from"
|
data.translatedText.length === 0
|
||||||
:class="m.translated_from ? 'text-success' : ''"
|
? m.message
|
||||||
:title="`Translated from ${ISO6391.getName(m.translated_from)}`"
|
: data.originalChat[id].message
|
||||||
class="ms-2 helpicon">
|
}}
|
||||||
<br/>
|
<span
|
||||||
{{ m.message }}
|
v-if="m.translated_from"
|
||||||
</span>
|
:class="m.translated_from ? 'text-success' : ''"
|
||||||
|
:title="`Translated from ${ISO6391.getName(m.translated_from)}`"
|
||||||
|
class="ms-2 helpicon"
|
||||||
|
>
|
||||||
|
<br/>
|
||||||
|
{{ m.message }}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -63,127 +98,126 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import {useStore} from "vuex";
|
|
||||||
import {onMounted, reactive} from "vue";
|
import {onMounted, reactive} from "vue";
|
||||||
import {constructAvatarUrl, ConvertTickToTime, FormatVacDate, GetChatHistory, GoToPlayer, truncate} from "@/utils";
|
import {
|
||||||
import TranslateChatButton from "@/components/TranslateChatButton";
|
constructAvatarUrl,
|
||||||
import ISO6391 from 'iso-639-1'
|
ConvertTickToTime,
|
||||||
|
FormatVacDate,
|
||||||
|
GetChatHistory,
|
||||||
|
GoToPlayer,
|
||||||
|
truncate,
|
||||||
|
} from "@/utils";
|
||||||
|
import TranslateChatButton from "@/components/TranslateChatButton.vue";
|
||||||
|
import ISO6391 from "iso-639-1";
|
||||||
|
import {useMatchDetailsStore} from "@/stores/matchDetails";
|
||||||
|
import {useInfoStateStore} from "@/stores/infoState";
|
||||||
|
import type {MatchChat, MatchChatItem, MatchStats} from "@/types";
|
||||||
|
|
||||||
export default {
|
const matchDetailsStore = useMatchDetailsStore();
|
||||||
name: "MatchChatHistory",
|
const infoStoreState = useInfoStateStore();
|
||||||
components: {TranslateChatButton},
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const data = reactive({
|
interface ChatStats extends MatchStats, MatchChatItem {
|
||||||
chat: [],
|
}
|
||||||
translatedText: [],
|
|
||||||
originalChat: [],
|
|
||||||
clientWidth: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleTranslatedText = async (e) => {
|
const data = reactive({
|
||||||
const [res, toggle] = await e
|
chat: [] as ChatStats[],
|
||||||
|
translatedText: [] as ChatStats[],
|
||||||
|
originalChat: [] as ChatStats[],
|
||||||
|
clientWidth: 0,
|
||||||
|
});
|
||||||
|
|
||||||
if (res !== null) {
|
const handleTranslatedText = (e: string) => {
|
||||||
if (toggle === 'translated') {
|
if (e === "translated") {
|
||||||
data.translatedText = await setPlayer(sortChatHistory(res, true))
|
data.translatedText = setPlayer(sortChatHistory(matchDetailsStore.matchChat, true));
|
||||||
data.chat = data.translatedText
|
data.chat = data.translatedText;
|
||||||
} else if (toggle === 'original') {
|
} else if (e === "original") {
|
||||||
data.chat = data.originalChat
|
data.chat = data.originalChat;
|
||||||
}
|
}
|
||||||
|
console.log(matchDetailsStore.matchChat)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getChatHistory = async () => {
|
||||||
|
const [resData, info] = await GetChatHistory(matchDetailsStore.matchDetails.match_id);
|
||||||
|
|
||||||
|
if (info.message !== "") infoStoreState.addInfo(info);
|
||||||
|
if (resData !== null) {
|
||||||
|
data.chat = setPlayer(sortChatHistory(resData));
|
||||||
|
data.originalChat = data.chat;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortChatHistory = (res: MatchChat = {}, translated = false): MatchChatItem[] => {
|
||||||
|
let arr = [] as MatchChatItem[];
|
||||||
|
if (res !== {}) {
|
||||||
|
Object.keys(res).forEach((i) => {
|
||||||
|
res[i].forEach((o) => {
|
||||||
|
let obj = Object.assign({
|
||||||
|
player: i,
|
||||||
|
tick: o.tick,
|
||||||
|
all_chat: o.all_chat,
|
||||||
|
message: o.message,
|
||||||
|
translated_from: translated ? o.translated_from : null,
|
||||||
|
translated_to: translated ? o.translated_to : null,
|
||||||
|
});
|
||||||
|
arr.push(obj);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
arr.sort((a, b) => a.tick - b.tick);
|
||||||
|
return arr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setPlayer = (chat: MatchChatItem[]): ChatStats[] => {
|
||||||
|
let arr: ChatStats[] = [];
|
||||||
|
for (const o of chat) {
|
||||||
|
for (const p of matchDetailsStore.matchDetails.stats || []) {
|
||||||
|
if (o.player === p.player?.steamid64) {
|
||||||
|
const obj: ChatStats = Object.assign({
|
||||||
|
player: truncate(p.player?.name || "", 20),
|
||||||
|
steamid64: p.player?.steamid64,
|
||||||
|
avatar: p.player?.avatar,
|
||||||
|
color: p.color,
|
||||||
|
startSide: p.team_id,
|
||||||
|
tracked: p.player?.tracked,
|
||||||
|
vac: p.player?.vac,
|
||||||
|
vac_date: p.player?.vac_date,
|
||||||
|
game_ban: p.player?.game_ban,
|
||||||
|
game_ban_date: p.player?.game_ban_date,
|
||||||
|
tick: o.tick,
|
||||||
|
tick_rate:
|
||||||
|
matchDetailsStore.matchDetails.tick_rate &&
|
||||||
|
matchDetailsStore.matchDetails.tick_rate !== -1
|
||||||
|
? matchDetailsStore.matchDetails.tick_rate
|
||||||
|
: 64,
|
||||||
|
all_chat: o.all_chat,
|
||||||
|
message: o.message,
|
||||||
|
translated_from: o.translated_from,
|
||||||
|
translated_to: o.translated_to,
|
||||||
|
});
|
||||||
|
arr.push(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getChatHistory = async () => {
|
|
||||||
const resData = await GetChatHistory(store, store.state.matchDetails.match_id)
|
|
||||||
if (resData !== null) {
|
|
||||||
data.chat = await setPlayer(sortChatHistory(resData))
|
|
||||||
data.originalChat = data.chat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sortChatHistory = (res = {}, translated = false) => {
|
|
||||||
let arr = []
|
|
||||||
if (res !== {}) {
|
|
||||||
Object.keys(res).forEach(i => {
|
|
||||||
res[i].forEach(o => {
|
|
||||||
let obj = Object.assign({
|
|
||||||
player: i,
|
|
||||||
tick: o.tick,
|
|
||||||
all_chat: o.all_chat,
|
|
||||||
message: o.message,
|
|
||||||
translated_from: translated ? o.translated_from : null,
|
|
||||||
translated_to: translated ? o.translated_to : null
|
|
||||||
})
|
|
||||||
arr.push(obj)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
arr.sort((a, b) => a.tick - b.tick)
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
|
|
||||||
const setPlayer = async (chat) => {
|
|
||||||
let arr = []
|
|
||||||
for (const o of chat) {
|
|
||||||
for (const p of store.state.matchDetails.stats) {
|
|
||||||
if (o.player === p.player.steamid64) {
|
|
||||||
const obj = Object.assign({
|
|
||||||
player: truncate(p.player.name, 20),
|
|
||||||
steamid64: p.player.steamid64,
|
|
||||||
avatar: p.player.avatar,
|
|
||||||
color: p.color,
|
|
||||||
startSide: p.team_id,
|
|
||||||
tracked: p.player.tracked,
|
|
||||||
vac: p.player.vac,
|
|
||||||
vac_date: p.player.vac_date,
|
|
||||||
game_ban: p.player.game_ban,
|
|
||||||
game_ban_date: p.player.game_ban_date,
|
|
||||||
tick: o.tick,
|
|
||||||
tick_rate: store.state.matchDetails.tick_rate && store.state.matchDetails.tick_rate !== -1 ? store.state.matchDetails.tick_rate : 64,
|
|
||||||
all_chat: o.all_chat,
|
|
||||||
message: o.message,
|
|
||||||
translated_from: o.translated_from,
|
|
||||||
translated_to: o.translated_to
|
|
||||||
})
|
|
||||||
arr.push(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
|
|
||||||
const sizeTable = () => {
|
|
||||||
if (document.documentElement.clientWidth <= 768) {
|
|
||||||
data.clientWidth = document.documentElement.clientWidth - 32
|
|
||||||
} else {
|
|
||||||
data.clientWidth = 700
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onresize = () => {
|
|
||||||
sizeTable()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getChatHistory()
|
|
||||||
sizeTable()
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
store,
|
|
||||||
ISO6391,
|
|
||||||
constructAvatarUrl,
|
|
||||||
GoToPlayer,
|
|
||||||
ConvertTickToTime,
|
|
||||||
FormatVacDate,
|
|
||||||
handleTranslatedText
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
return arr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sizeTable = () => {
|
||||||
|
if (document.documentElement.clientWidth <= 768) {
|
||||||
|
data.clientWidth = document.documentElement.clientWidth - 32;
|
||||||
|
} else {
|
||||||
|
data.clientWidth = 700;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onresize = () => {
|
||||||
|
sizeTable();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getChatHistory();
|
||||||
|
sizeTable();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -195,11 +229,11 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.translate-btn {
|
.translate-btn {
|
||||||
margin-top: .5rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
padding: .5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.td-time {
|
.td-time {
|
||||||
@@ -226,8 +260,8 @@ td {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
.tracked {
|
.tracked {
|
||||||
font-size: .8rem;
|
font-size: 0.8rem;
|
||||||
margin-right: .2rem;
|
margin-right: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ban-shadow {
|
.ban-shadow {
|
||||||
|
|||||||
@@ -1,131 +1,232 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="props.matches.length === 0" id="matches-placeholder">
|
<div v-if="props.matches.length === 0" id="matches-placeholder">
|
||||||
<span v-for="i in 20" :key="i" :class="i % 2 === 1 ? 'placeholder-wave' : 'placeholder-wave-alt'"
|
<span
|
||||||
class="placeholder col-12"></span>
|
v-for="i in 20"
|
||||||
|
:key="i"
|
||||||
|
:class="i % 2 === 1 ? 'placeholder-wave' : 'placeholder-wave-alt'"
|
||||||
|
class="placeholder col-12"
|
||||||
|
></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else id="matches">
|
<div v-else id="matches">
|
||||||
<table class="table table-borderless">
|
<table class="table table-borderless">
|
||||||
<thead class="border-bottom">
|
<thead class="border-bottom">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-center map" scope="col">Map</th>
|
<th class="text-center map" scope="col">Map</th>
|
||||||
<th class="text-center rank" scope="col">Rank</th>
|
<th class="text-center rank" scope="col">Rank</th>
|
||||||
<th class="text-center length" scope="col" title="Match Length">
|
<th class="text-center length" scope="col" title="Match Length">
|
||||||
<img alt="Match length" class="match-len helpicon" src="/images/icons/timer_both.svg">
|
<img
|
||||||
</th>
|
alt="Match length"
|
||||||
<th class="text-center score" scope="col">Score</th>
|
class="match-len helpicon"
|
||||||
<th v-if="!props.explore" class="text-center kills" scope="col">K</th>
|
src="/images/icons/timer_both.svg"
|
||||||
<th v-if="!props.explore" class="text-center assists" scope="col">A</th>
|
/>
|
||||||
<th v-if="!props.explore" class="text-center deaths" scope="col">D</th>
|
</th>
|
||||||
<th v-if="!props.explore" class="text-center kdiff helptext" scope="col" title="Kill-to-death difference">+/-</th>
|
<th class="text-center score" scope="col">Score</th>
|
||||||
<th v-if="!props.explore" class="text-center hltv helptext" scope="col" title="HLTV 1.0 Rating">Rating</th>
|
<th v-if="!props.explore" class="text-center kills" scope="col">K</th>
|
||||||
<th class="text-center duration" scope="col">Duration</th>
|
<th v-if="!props.explore" class="text-center assists" scope="col">
|
||||||
<th class="date" scope="col">Date</th>
|
A
|
||||||
</tr>
|
</th>
|
||||||
|
<th v-if="!props.explore" class="text-center deaths" scope="col">
|
||||||
|
D
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
v-if="!props.explore"
|
||||||
|
class="text-center kdiff helptext"
|
||||||
|
scope="col"
|
||||||
|
title="Kill-to-death difference"
|
||||||
|
>
|
||||||
|
+/-
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
v-if="!props.explore"
|
||||||
|
class="text-center hltv helptext"
|
||||||
|
scope="col"
|
||||||
|
title="HLTV 1.0 Rating"
|
||||||
|
>
|
||||||
|
Rating
|
||||||
|
</th>
|
||||||
|
<th class="text-center duration" scope="col">Duration</th>
|
||||||
|
<th class="date" scope="col">Date</th>
|
||||||
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="match in props.matches"
|
<tr
|
||||||
|
v-for="match in props.matches"
|
||||||
:key="match.match_id"
|
:key="match.match_id"
|
||||||
:class="props.colorFront ? (GetWinLoss(match.match_result, match.stats.team_id) + (match.vac || match.game_ban ? ' ban' : '')) : (match.vac || match.game_ban ? ' matches_ban' : '')"
|
:class="
|
||||||
:title="match.vac ? 'VAC-banned player in this game' : match.game_ban ? 'Game-banned player in this game' : ''"
|
props.colorFront
|
||||||
|
? GetWinLoss(match.match_result, match.stats.team_id) +
|
||||||
|
(match.vac || match.game_ban ? ' ban' : '')
|
||||||
|
: match.vac || match.game_ban
|
||||||
|
? ' matches_ban'
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
:title="
|
||||||
|
match.vac
|
||||||
|
? 'VAC-banned player in this game'
|
||||||
|
: match.game_ban
|
||||||
|
? 'Game-banned player in this game'
|
||||||
|
: ''
|
||||||
|
"
|
||||||
class="match default"
|
class="match default"
|
||||||
@click="GoToMatch(match.match_id)"
|
@click="GoToMatch(match.match_id)"
|
||||||
>
|
>
|
||||||
<td class="td-map text-center">
|
<td class="td-map text-center">
|
||||||
<i v-if="match.parsed" class="fa fa-bar-chart parsed helpicon"
|
<i
|
||||||
title="Demo has been parsed for additional data"></i>
|
v-if="match.parsed"
|
||||||
<i v-if="!match.parsed && MatchNotParsedTime(match.date)" class="fa fa-hourglass-half not-yet-parsed helpicon"
|
class="fa fa-bar-chart parsed helpicon"
|
||||||
title="Match has not been parsed yet"></i>
|
title="Demo has been parsed for additional data"
|
||||||
<img v-if="match.map !== ''"
|
></i>
|
||||||
:alt="match.map"
|
<i
|
||||||
:src="'/images/map_icons/map_icon_' + match.map + '.svg'"
|
v-if="!match.parsed && MatchNotParsedTime(match.date)"
|
||||||
:title="FixMapName(match.map)"
|
class="fa fa-hourglass-half not-yet-parsed helpicon"
|
||||||
class="map-icon">
|
title="Match has not been parsed yet"
|
||||||
<i v-else class="fa fa-question-circle-o map-not-found" title="Match not parsed"></i>
|
></i>
|
||||||
</td>
|
<img
|
||||||
<td class="td-rank text-center">
|
v-if="match.map !== ''"
|
||||||
<img v-if="props.explore"
|
:alt="match.map"
|
||||||
:alt="DisplayRank(Math.floor(match.avg_rank || 0))[1]"
|
:src="'/images/map_icons/map_icon_' + match.map + '.svg'"
|
||||||
:src="DisplayRank(Math.floor(match.avg_rank || 0))[0]"
|
:title="FixMapName(match.map)"
|
||||||
:title="DisplayRank(Math.floor(match.avg_rank || 0))[1]" class="rank-icon">
|
class="map-icon"
|
||||||
<img v-else
|
/>
|
||||||
:alt="DisplayRank(match.stats.rank?.new)[1]"
|
<i
|
||||||
:class="match.stats.rank?.new > match.stats.rank?.old ? 'uprank' : match.stats.rank?.new < match.stats.rank?.old ? 'downrank' : ''"
|
v-else
|
||||||
:src="DisplayRank(match.stats.rank?.new)[0]"
|
class="fa fa-question-circle-o map-not-found"
|
||||||
:title="DisplayRank(match.stats.rank?.new)[1]" class="rank-icon">
|
title="Match not parsed"
|
||||||
</td>
|
></i>
|
||||||
<td class="td-length text-center">
|
</td>
|
||||||
<img v-if="match.max_rounds === 30 || !match.max_rounds"
|
<td class="td-rank text-center">
|
||||||
alt="Match long"
|
<img
|
||||||
class="match-len"
|
v-if="props.explore"
|
||||||
src="/images/icons/timer_long.svg"
|
:alt="DisplayRank(Math.floor(match.avg_rank || 0))[1]"
|
||||||
title="Long Match">
|
:src="DisplayRank(Math.floor(match.avg_rank || 0))[0]"
|
||||||
<img v-if="match.max_rounds === 16"
|
:title="DisplayRank(Math.floor(match.avg_rank || 0))[1]"
|
||||||
alt="Match short"
|
class="rank-icon"
|
||||||
class="match-len"
|
/>
|
||||||
src="/images/icons/timer_short.svg"
|
<img
|
||||||
title="Short Match">
|
v-else
|
||||||
</td>
|
:alt="DisplayRank(match.stats.rank?.new)[1]"
|
||||||
<td class="td-score text-center fw-bold">
|
:class="
|
||||||
<span
|
match.stats.rank?.new > match.stats.rank?.old
|
||||||
:class="match.match_result === 1 ? 'text-success' : match.match_result === 0 ? 'text-warning' : 'text-danger'">{{
|
? 'uprank'
|
||||||
match.score[0]
|
: match.stats.rank?.new < match.stats.rank?.old
|
||||||
}}</span> - <span
|
? 'downrank'
|
||||||
:class="match.match_result === 2 ? 'text-success' : match.match_result === 0 ? 'text-warning' : 'text-danger'">{{
|
: ''
|
||||||
match.score[1]
|
"
|
||||||
}}</span>
|
:src="DisplayRank(match.stats.rank?.new)[0]"
|
||||||
</td>
|
:title="DisplayRank(match.stats.rank?.new)[1]"
|
||||||
<td v-if="match.stats" class="td-kills text-center">
|
class="rank-icon"
|
||||||
{{ match.stats.kills ? match.stats.kills : "0" }}
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td v-if="match.stats" class="td-assists text-center">
|
<td class="td-length text-center">
|
||||||
{{ match.stats.assists ? match.stats.assists : "0" }}
|
<img
|
||||||
</td>
|
v-if="match.max_rounds === 30 || !match.max_rounds"
|
||||||
<td v-if="match.stats" class="td-deaths text-center">
|
alt="Match long"
|
||||||
{{ match.stats.deaths ? match.stats.deaths : "0" }}
|
class="match-len"
|
||||||
</td>
|
src="/images/icons/timer_long.svg"
|
||||||
<td v-if="match.stats"
|
title="Long Match"
|
||||||
:class="(match.stats.kills ? match.stats.kills : 0) - (match.stats.deaths ? match.stats.deaths : 0) >= 0 ? 'text-success' : 'text-danger'"
|
/>
|
||||||
class="td-plus text-center">
|
<img
|
||||||
{{
|
v-if="match.max_rounds === 16"
|
||||||
(match.stats.kills ? match.stats.kills : 0) - (match.stats.deaths ? match.stats.deaths : 0)
|
alt="Match short"
|
||||||
}}
|
class="match-len"
|
||||||
</td>
|
src="/images/icons/timer_short.svg"
|
||||||
<td v-if="match.stats"
|
title="Short Match"
|
||||||
:class="GetHLTV_1(
|
/>
|
||||||
match.stats.kills,
|
</td>
|
||||||
match.score[0] + match.score[1],
|
<td class="td-score text-center fw-bold">
|
||||||
match.stats.deaths,
|
<span
|
||||||
match.stats.multi_kills?.duo,
|
:class="
|
||||||
match.stats.multi_kills?.triple,
|
match.match_result === 1
|
||||||
match.stats.multi_kills?.quad,
|
? 'text-success'
|
||||||
match.stats.multi_kills?.pent) >= 1 ? 'text-success' : 'text-warning'"
|
: match.match_result === 0
|
||||||
class="td-hltv text-center fw-bold">
|
? 'text-warning'
|
||||||
{{
|
: 'text-danger'
|
||||||
GetHLTV_1(
|
"
|
||||||
|
>{{ match.score[0] }}</span
|
||||||
|
>
|
||||||
|
-
|
||||||
|
<span
|
||||||
|
:class="
|
||||||
|
match.match_result === 2
|
||||||
|
? 'text-success'
|
||||||
|
: match.match_result === 0
|
||||||
|
? 'text-warning'
|
||||||
|
: 'text-danger'
|
||||||
|
"
|
||||||
|
>{{ match.score[1] }}</span
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td v-if="match.stats" class="td-kills text-center">
|
||||||
|
{{ match.stats.kills ? match.stats.kills : "0" }}
|
||||||
|
</td>
|
||||||
|
<td v-if="match.stats" class="td-assists text-center">
|
||||||
|
{{ match.stats.assists ? match.stats.assists : "0" }}
|
||||||
|
</td>
|
||||||
|
<td v-if="match.stats" class="td-deaths text-center">
|
||||||
|
{{ match.stats.deaths ? match.stats.deaths : "0" }}
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
v-if="match.stats"
|
||||||
|
:class="
|
||||||
|
(match.stats.kills ? match.stats.kills : 0) -
|
||||||
|
(match.stats.deaths ? match.stats.deaths : 0) >=
|
||||||
|
0
|
||||||
|
? 'text-success'
|
||||||
|
: 'text-danger'
|
||||||
|
"
|
||||||
|
class="td-plus text-center"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
(match.stats.kills ? match.stats.kills : 0) -
|
||||||
|
(match.stats.deaths ? match.stats.deaths : 0)
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
v-if="match.stats"
|
||||||
|
:class="
|
||||||
|
GetHLTV_1(
|
||||||
match.stats.kills,
|
match.stats.kills,
|
||||||
match.score[0] + match.score[1],
|
match.score[0] + match.score[1],
|
||||||
match.stats.deaths,
|
match.stats.deaths,
|
||||||
match.stats.multi_kills?.duo,
|
match.stats.multi_kills?.duo,
|
||||||
match.stats.multi_kills?.triple,
|
match.stats.multi_kills?.triple,
|
||||||
match.stats.multi_kills?.quad,
|
match.stats.multi_kills?.quad,
|
||||||
match.stats.multi_kills?.pent)
|
match.stats.multi_kills?.pent
|
||||||
}}
|
) >= 1
|
||||||
</td>
|
? 'text-success'
|
||||||
<td :title="FormatFullDuration(match.duration)" class="td-duration text-center">
|
: 'text-warning'
|
||||||
{{ FormatDuration(match.duration) }}
|
"
|
||||||
|
class="td-hltv text-center fw-bold"
|
||||||
</td>
|
>
|
||||||
<td :title="FormatFullDate(match.date)" class="td-date">
|
{{
|
||||||
{{ FormatDate(match.date) }}
|
GetHLTV_1(
|
||||||
</td>
|
match.stats.kills,
|
||||||
</tr>
|
match.score[0] + match.score[1],
|
||||||
|
match.stats.deaths,
|
||||||
|
match.stats.multi_kills?.duo,
|
||||||
|
match.stats.multi_kills?.triple,
|
||||||
|
match.stats.multi_kills?.quad,
|
||||||
|
match.stats.multi_kills?.pent
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
:title="FormatFullDuration(match.duration)"
|
||||||
|
class="td-duration text-center"
|
||||||
|
>
|
||||||
|
{{ FormatDuration(match.duration) }}
|
||||||
|
</td>
|
||||||
|
<td :title="FormatFullDate(match.date)" class="td-date">
|
||||||
|
{{ FormatDate(match.date) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
DisplayRank,
|
DisplayRank,
|
||||||
FixMapName,
|
FixMapName,
|
||||||
@@ -136,47 +237,24 @@ import {
|
|||||||
GetHLTV_1,
|
GetHLTV_1,
|
||||||
GetWinLoss,
|
GetWinLoss,
|
||||||
GoToMatch,
|
GoToMatch,
|
||||||
MatchNotParsedTime
|
MatchNotParsedTime,
|
||||||
} from "@/utils";
|
} from "@/utils";
|
||||||
|
import { defineProps } from "vue";
|
||||||
|
import type { Match } from "@/types";
|
||||||
|
|
||||||
export default {
|
interface Props {
|
||||||
name: "MatchesTable",
|
colorFront?: boolean;
|
||||||
props: {
|
matches?: Match[];
|
||||||
colorFront: {
|
explore?: boolean;
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
matches: {
|
|
||||||
type: Array,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
explore: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
return {
|
|
||||||
props,
|
|
||||||
FormatDate,
|
|
||||||
FormatFullDate,
|
|
||||||
FormatDuration,
|
|
||||||
FormatFullDuration,
|
|
||||||
GetHLTV_1,
|
|
||||||
GetWinLoss,
|
|
||||||
GoToMatch,
|
|
||||||
MatchNotParsedTime,
|
|
||||||
DisplayRank,
|
|
||||||
FixMapName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
colorFront: false,
|
||||||
|
explore: false,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
#matches-placeholder {
|
#matches-placeholder {
|
||||||
.placeholder {
|
.placeholder {
|
||||||
height: 78px;
|
height: 78px;
|
||||||
@@ -197,7 +275,8 @@ table {
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
th:last-child, td:last-child {
|
th:last-child,
|
||||||
|
td:last-child {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 150px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
@@ -242,7 +321,7 @@ table {
|
|||||||
top: 4px;
|
top: 4px;
|
||||||
left: 48px;
|
left: 48px;
|
||||||
font-size: 4.35rem;
|
font-size: 4.35rem;
|
||||||
color: rgba(255, 193, 7, .86);
|
color: rgba(255, 193, 7, 0.86);
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@@ -266,7 +345,8 @@ table {
|
|||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.td-date, .date {
|
.td-date,
|
||||||
|
.date {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -282,115 +362,125 @@ table {
|
|||||||
$ban: false;
|
$ban: false;
|
||||||
|
|
||||||
&.default {
|
&.default {
|
||||||
background: linear-gradient(to right,
|
background: linear-gradient(
|
||||||
rgba($first, 0.2) 0%,
|
to right,
|
||||||
rgba($first, 0.1) 15%,
|
rgba($first, 0.2) 0%,
|
||||||
rgba(0, 0, 0, 0.4) 30%,
|
rgba($first, 0.1) 15%,
|
||||||
rgba(0, 0, 0, 0.4) 70%,
|
rgba(0, 0, 0, 0.4) 30%,
|
||||||
rgba($last, 0.6) 80%,
|
rgba(0, 0, 0, 0.4) 70%,
|
||||||
rgba($last, 0.6) 100%
|
rgba($last, 0.6) 80%,
|
||||||
|
rgba($last, 0.6) 100%
|
||||||
);
|
);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: linear-gradient(to right,
|
background: linear-gradient(
|
||||||
rgba($first, 0.3) 0%,
|
to right,
|
||||||
rgba($first, 0.2) 15%,
|
rgba($first, 0.3) 0%,
|
||||||
rgba(0, 0, 0, 0.5) 30%,
|
rgba($first, 0.2) 15%,
|
||||||
rgba(0, 0, 0, 0.5) 70%,
|
rgba(0, 0, 0, 0.5) 30%,
|
||||||
rgba($last, 0.7) 80%,
|
rgba(0, 0, 0, 0.5) 70%,
|
||||||
rgba($last, 0.7) 100%
|
rgba($last, 0.7) 80%,
|
||||||
|
rgba($last, 0.7) 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.win {
|
&.win {
|
||||||
$first: rgb(0, 255, 0);
|
$first: rgb(0, 255, 0);
|
||||||
background: linear-gradient(to right,
|
background: linear-gradient(
|
||||||
rgba($first, 0.2) 0%,
|
to right,
|
||||||
rgba($first, 0.1) 15%,
|
rgba($first, 0.2) 0%,
|
||||||
rgba(0, 0, 0, 0.4) 30%,
|
rgba($first, 0.1) 15%,
|
||||||
rgba(0, 0, 0, 0.4) 70%,
|
rgba(0, 0, 0, 0.4) 30%,
|
||||||
rgba($last, 0.6) 80%,
|
rgba(0, 0, 0, 0.4) 70%,
|
||||||
rgba($last, 0.6) 100%
|
rgba($last, 0.6) 80%,
|
||||||
|
rgba($last, 0.6) 100%
|
||||||
);
|
);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: linear-gradient(to right,
|
background: linear-gradient(
|
||||||
rgba($first, 0.3) 0%,
|
to right,
|
||||||
rgba($first, 0.2) 15%,
|
rgba($first, 0.3) 0%,
|
||||||
rgba(0, 0, 0, 0.5) 30%,
|
rgba($first, 0.2) 15%,
|
||||||
rgba(0, 0, 0, 0.5) 70%,
|
rgba(0, 0, 0, 0.5) 30%,
|
||||||
rgba($last, 0.7) 80%,
|
rgba(0, 0, 0, 0.5) 70%,
|
||||||
rgba($last, 0.7) 100%
|
rgba($last, 0.7) 80%,
|
||||||
|
rgba($last, 0.7) 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.draw {
|
&.draw {
|
||||||
$first: rgb(255, 255, 0);
|
$first: rgb(255, 255, 0);
|
||||||
background: linear-gradient(to right,
|
background: linear-gradient(
|
||||||
rgba($first, 0.2) 0%,
|
to right,
|
||||||
rgba($first, 0.1) 15%,
|
rgba($first, 0.2) 0%,
|
||||||
rgba(0, 0, 0, 0.4) 30%,
|
rgba($first, 0.1) 15%,
|
||||||
rgba(0, 0, 0, 0.4) 70%,
|
rgba(0, 0, 0, 0.4) 30%,
|
||||||
rgba($last, 0.6) 80%,
|
rgba(0, 0, 0, 0.4) 70%,
|
||||||
rgba($last, 0.6) 100%
|
rgba($last, 0.6) 80%,
|
||||||
|
rgba($last, 0.6) 100%
|
||||||
);
|
);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: linear-gradient(to right,
|
background: linear-gradient(
|
||||||
rgba($first, 0.3) 0%,
|
to right,
|
||||||
rgba($first, 0.2) 15%,
|
rgba($first, 0.3) 0%,
|
||||||
rgba(0, 0, 0, 0.5) 30%,
|
rgba($first, 0.2) 15%,
|
||||||
rgba(0, 0, 0, 0.5) 70%,
|
rgba(0, 0, 0, 0.5) 30%,
|
||||||
rgba($last, 0.7) 80%,
|
rgba(0, 0, 0, 0.5) 70%,
|
||||||
rgba($last, 0.7) 100%
|
rgba($last, 0.7) 80%,
|
||||||
|
rgba($last, 0.7) 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.loss {
|
&.loss {
|
||||||
$first: rgb(255, 0, 0);
|
$first: rgb(255, 0, 0);
|
||||||
background: linear-gradient(to right,
|
background: linear-gradient(
|
||||||
rgba($first, 0.2) 0%,
|
to right,
|
||||||
rgba($first, 0.1) 15%,
|
rgba($first, 0.2) 0%,
|
||||||
rgba(0, 0, 0, 0.4) 30%,
|
rgba($first, 0.1) 15%,
|
||||||
rgba(0, 0, 0, 0.4) 70%,
|
rgba(0, 0, 0, 0.4) 30%,
|
||||||
rgba($last, 0.6) 80%,
|
rgba(0, 0, 0, 0.4) 70%,
|
||||||
rgba($last, 0.6) 100%
|
rgba($last, 0.6) 80%,
|
||||||
|
rgba($last, 0.6) 100%
|
||||||
);
|
);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: linear-gradient(to right,
|
background: linear-gradient(
|
||||||
rgba($first, 0.3) 0%,
|
to right,
|
||||||
rgba($first, 0.2) 15%,
|
rgba($first, 0.3) 0%,
|
||||||
rgba(0, 0, 0, 0.5) 30%,
|
rgba($first, 0.2) 15%,
|
||||||
rgba(0, 0, 0, 0.5) 70%,
|
rgba(0, 0, 0, 0.5) 30%,
|
||||||
rgba($last, 0.7) 80%,
|
rgba(0, 0, 0, 0.5) 70%,
|
||||||
rgba($last, 0.7) 100%
|
rgba($last, 0.7) 80%,
|
||||||
|
rgba($last, 0.7) 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ban {
|
&.ban {
|
||||||
$last: rgb(93, 3, 3);
|
$last: rgb(93, 3, 3);
|
||||||
background: linear-gradient(to right,
|
background: linear-gradient(
|
||||||
rgba($first, 0.2) 0%,
|
to right,
|
||||||
rgba($first, 0.1) 15%,
|
rgba($first, 0.2) 0%,
|
||||||
rgba(0, 0, 0, 0.4) 30%,
|
rgba($first, 0.1) 15%,
|
||||||
rgba(0, 0, 0, 0.4) 70%,
|
rgba(0, 0, 0, 0.4) 30%,
|
||||||
rgba($last, 0.6) 80%,
|
rgba(0, 0, 0, 0.4) 70%,
|
||||||
rgba($last, 0.6) 100%
|
rgba($last, 0.6) 80%,
|
||||||
|
rgba($last, 0.6) 100%
|
||||||
);
|
);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: linear-gradient(to right,
|
background: linear-gradient(
|
||||||
rgba($first, 0.3) 0%,
|
to right,
|
||||||
rgba($first, 0.2) 15%,
|
rgba($first, 0.3) 0%,
|
||||||
rgba(0, 0, 0, 0.5) 30%,
|
rgba($first, 0.2) 15%,
|
||||||
rgba(0, 0, 0, 0.5) 70%,
|
rgba(0, 0, 0, 0.5) 30%,
|
||||||
rgba($last, 0.7) 80%,
|
rgba(0, 0, 0, 0.5) 70%,
|
||||||
rgba($last, 0.7) 100%
|
rgba($last, 0.7) 80%,
|
||||||
|
rgba($last, 0.7) 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -398,23 +488,25 @@ table {
|
|||||||
&.matches_ban {
|
&.matches_ban {
|
||||||
$first: rgb(0, 0, 0);
|
$first: rgb(0, 0, 0);
|
||||||
$last: rgb(93, 3, 3);
|
$last: rgb(93, 3, 3);
|
||||||
background: linear-gradient(to right,
|
background: linear-gradient(
|
||||||
rgba($first, 0.2) 0%,
|
to right,
|
||||||
rgba($first, 0.1) 15%,
|
rgba($first, 0.2) 0%,
|
||||||
rgba(0, 0, 0, 0.4) 30%,
|
rgba($first, 0.1) 15%,
|
||||||
rgba(0, 0, 0, 0.4) 70%,
|
rgba(0, 0, 0, 0.4) 30%,
|
||||||
rgba($last, 0.6) 80%,
|
rgba(0, 0, 0, 0.4) 70%,
|
||||||
rgba($last, 0.6) 100%
|
rgba($last, 0.6) 80%,
|
||||||
|
rgba($last, 0.6) 100%
|
||||||
);
|
);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: linear-gradient(to right,
|
background: linear-gradient(
|
||||||
rgba($first, 0.3) 0%,
|
to right,
|
||||||
rgba($first, 0.2) 15%,
|
rgba($first, 0.3) 0%,
|
||||||
rgba(0, 0, 0, 0.5) 30%,
|
rgba($first, 0.2) 15%,
|
||||||
rgba(0, 0, 0, 0.5) 70%,
|
rgba(0, 0, 0, 0.5) 30%,
|
||||||
rgba($last, 0.7) 80%,
|
rgba(0, 0, 0, 0.5) 70%,
|
||||||
rgba($last, 0.7) 100%
|
rgba($last, 0.7) 80%,
|
||||||
|
rgba($last, 0.7) 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -433,23 +525,25 @@ table {
|
|||||||
|
|
||||||
@media screen and (max-width: 400px) {
|
@media screen and (max-width: 400px) {
|
||||||
table tr {
|
table tr {
|
||||||
.map-icon {
|
.map-icon {
|
||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
}
|
}
|
||||||
.map {
|
|
||||||
padding: 0.5rem !important;
|
|
||||||
}
|
|
||||||
.td-map {
|
|
||||||
padding: 0 1rem !important;
|
|
||||||
|
|
||||||
.parsed {
|
.map {
|
||||||
display: none;
|
padding: 0.5rem !important;
|
||||||
}
|
}
|
||||||
.not-yet-parsed {
|
|
||||||
display: none;
|
.td-map {
|
||||||
|
padding: 0 1rem !important;
|
||||||
|
|
||||||
|
.parsed {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.not-yet-parsed {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
@@ -463,12 +557,12 @@ table {
|
|||||||
|
|
||||||
.parsed {
|
.parsed {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: .3rem !important;
|
left: 0.3rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.not-yet-parsed {
|
.not-yet-parsed {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: .3rem !important;
|
left: 0.3rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@@ -484,16 +578,28 @@ table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.td-score {
|
.td-score {
|
||||||
font-size: .7rem !important;
|
font-size: 0.7rem !important;
|
||||||
//width: 110px !important;
|
//width: 110px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.td-date {
|
.td-date {
|
||||||
font-size: .8rem !important;
|
font-size: 0.8rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kills, .deaths, .assists, .kdiff, .duration, .hltv, .length,
|
.kills,
|
||||||
.td-kills, .td-deaths, .td-assists, .td-plus, .td-duration, .td-hltv, .td-length {
|
.deaths,
|
||||||
|
.assists,
|
||||||
|
.kdiff,
|
||||||
|
.duration,
|
||||||
|
.hltv,
|
||||||
|
.length,
|
||||||
|
.td-kills,
|
||||||
|
.td-deaths,
|
||||||
|
.td-assists,
|
||||||
|
.td-plus,
|
||||||
|
.td-duration,
|
||||||
|
.td-hltv,
|
||||||
|
.td-length {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -506,13 +612,15 @@ table {
|
|||||||
.trackme-btn {
|
.trackme-btn {
|
||||||
top: 25px;
|
top: 25px;
|
||||||
}
|
}
|
||||||
.map, .td-map {
|
.map,
|
||||||
|
.td-map {
|
||||||
padding-left: 4rem !important;
|
padding-left: 4rem !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1200px) {
|
@media screen and (max-width: 1200px) {
|
||||||
.td-plus, .kdiff {
|
.td-plus,
|
||||||
|
.kdiff {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.td-rank img {
|
.td-rank img {
|
||||||
|
|||||||
@@ -6,140 +6,177 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as echarts from 'echarts/core';
|
import * as echarts from "echarts/core";
|
||||||
import {GridComponent, TooltipComponent, VisualMapComponent} from 'echarts/components';
|
import {
|
||||||
import {HeatmapChart} from 'echarts/charts';
|
GridComponent,
|
||||||
import {CanvasRenderer} from 'echarts/renderers';
|
TooltipComponent,
|
||||||
import {onMounted, onUnmounted, ref} from "vue";
|
VisualMapComponent,
|
||||||
import {checkStatEmpty, getPlayerArr} from "../utils";
|
} from "echarts/components";
|
||||||
import {useStore} from "vuex";
|
import { HeatmapChart } from "echarts/charts";
|
||||||
|
import { CanvasRenderer } from "echarts/renderers";
|
||||||
|
import { onMounted, onUnmounted, ref } from "vue";
|
||||||
|
import { checkStatEmpty, getPlayerArr } from "../utils";
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MultiKillsChart",
|
name: "MultiKillsChart",
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore()
|
const store = useStore();
|
||||||
|
|
||||||
const multiKills = ['2k', '3k', '4k', '5k']
|
const multiKills = ["2k", "3k", "4k", "5k"];
|
||||||
let myChart1, myChart2
|
let myChart1, myChart2;
|
||||||
const width = ref(window.innerWidth <= 500 ? window.innerWidth : 500)
|
const width = ref(window.innerWidth <= 500 ? window.innerWidth : 500);
|
||||||
const height = ref(width.value)
|
const height = ref(width.value);
|
||||||
|
|
||||||
const multiKillArr = (stats, team) => {
|
const multiKillArr = (stats, team) => {
|
||||||
let arr = []
|
let arr = [];
|
||||||
for (let i = (team - 1) * 5; i < team * 5; i++) {
|
for (let i = (team - 1) * 5; i < team * 5; i++) {
|
||||||
for (let j = 0; j < multiKills.length; j++) {
|
for (let j = 0; j < multiKills.length; j++) {
|
||||||
if (j === 0)
|
if (j === 0)
|
||||||
arr.push([i % 5, j, checkStatEmpty(stats[i].multi_kills.duo) === 0 ? null : stats[i].multi_kills.duo])
|
arr.push([
|
||||||
|
i % 5,
|
||||||
|
j,
|
||||||
|
checkStatEmpty(stats[i].multi_kills.duo) === 0
|
||||||
|
? null
|
||||||
|
: stats[i].multi_kills.duo,
|
||||||
|
]);
|
||||||
if (j === 1)
|
if (j === 1)
|
||||||
arr.push([i % 5, j, checkStatEmpty(stats[i].multi_kills.triple) === 0 ? null : stats[i].multi_kills.triple])
|
arr.push([
|
||||||
|
i % 5,
|
||||||
|
j,
|
||||||
|
checkStatEmpty(stats[i].multi_kills.triple) === 0
|
||||||
|
? null
|
||||||
|
: stats[i].multi_kills.triple,
|
||||||
|
]);
|
||||||
if (j === 2)
|
if (j === 2)
|
||||||
arr.push([i % 5, j, checkStatEmpty(stats[i].multi_kills.quad) === 0 ? null : stats[i].multi_kills.quad])
|
arr.push([
|
||||||
|
i % 5,
|
||||||
|
j,
|
||||||
|
checkStatEmpty(stats[i].multi_kills.quad) === 0
|
||||||
|
? null
|
||||||
|
: stats[i].multi_kills.quad,
|
||||||
|
]);
|
||||||
if (j === 3)
|
if (j === 3)
|
||||||
arr.push([i % 5, j, checkStatEmpty(stats[i].multi_kills.pent) === 0 ? null : stats[i].multi_kills.pent])
|
arr.push([
|
||||||
|
i % 5,
|
||||||
|
j,
|
||||||
|
checkStatEmpty(stats[i].multi_kills.pent) === 0
|
||||||
|
? null
|
||||||
|
: stats[i].multi_kills.pent,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return arr
|
return arr;
|
||||||
}
|
};
|
||||||
|
|
||||||
const getMax = (stats, team) => {
|
const getMax = (stats, team) => {
|
||||||
let max = 0
|
let max = 0;
|
||||||
for (let i = (team - 1) * 5; i < team * 5; i++) {
|
for (let i = (team - 1) * 5; i < team * 5; i++) {
|
||||||
if (stats[i].multi_kills.duo > max)
|
if (stats[i].multi_kills.duo > max) max = stats[i].multi_kills.duo;
|
||||||
max = stats[i].multi_kills.duo
|
|
||||||
if (stats[i].multi_kills.triple > max)
|
if (stats[i].multi_kills.triple > max)
|
||||||
max = stats[i].multi_kills.triple
|
max = stats[i].multi_kills.triple;
|
||||||
if (stats[i].multi_kills.quad > max)
|
if (stats[i].multi_kills.quad > max) max = stats[i].multi_kills.quad;
|
||||||
max = stats[i].multi_kills.quad
|
if (stats[i].multi_kills.pent > max) max = stats[i].multi_kills.pent;
|
||||||
if (stats[i].multi_kills.pent > max)
|
|
||||||
max = stats[i].multi_kills.pent
|
|
||||||
}
|
}
|
||||||
return max
|
return max;
|
||||||
}
|
};
|
||||||
|
|
||||||
const optionGen = (team) => {
|
const optionGen = (team) => {
|
||||||
return {
|
return {
|
||||||
tooltip: {},
|
tooltip: {},
|
||||||
grid: {
|
grid: {
|
||||||
height: '65%',
|
height: "65%",
|
||||||
top: '0%',
|
top: "0%",
|
||||||
bottom: '10%'
|
bottom: "10%",
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: "category",
|
||||||
data: getPlayerArr(store.state.matchDetails.stats, team, true).reverse(),
|
data: getPlayerArr(
|
||||||
|
store.state.matchDetails.stats,
|
||||||
|
team,
|
||||||
|
true
|
||||||
|
).reverse(),
|
||||||
splitArea: {
|
splitArea: {
|
||||||
show: true
|
show: true,
|
||||||
},
|
},
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: 'white',
|
color: "white",
|
||||||
rotate: 50
|
rotate: 50,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'category',
|
type: "category",
|
||||||
data: multiKills,
|
data: multiKills,
|
||||||
splitArea: {
|
splitArea: {
|
||||||
show: true
|
show: true,
|
||||||
},
|
},
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: 'white'
|
color: "white",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
visualMap: {
|
visualMap: {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: getMax(store.state.matchDetails.stats, team),
|
max: getMax(store.state.matchDetails.stats, team),
|
||||||
calculable: true,
|
calculable: true,
|
||||||
orient: 'horizontal',
|
orient: "horizontal",
|
||||||
left: 'center',
|
left: "center",
|
||||||
bottom: '5%',
|
bottom: "5%",
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: 'white'
|
color: "white",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
type: 'heatmap',
|
type: "heatmap",
|
||||||
data: multiKillArr(store.state.matchDetails.stats, team),
|
data: multiKillArr(store.state.matchDetails.stats, team),
|
||||||
label: {
|
label: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
show: true
|
show: true,
|
||||||
},
|
},
|
||||||
emphasis: {
|
emphasis: {
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
shadowBlur: 10,
|
shadowBlur: 10,
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
shadowColor: "rgba(0, 0, 0, 0.5)",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const disposeCharts = () => {
|
const disposeCharts = () => {
|
||||||
if (myChart1 != null && myChart1 !== '' && myChart1 !== undefined) {
|
if (myChart1 != null && myChart1 !== "" && myChart1 !== undefined) {
|
||||||
myChart1.dispose()
|
myChart1.dispose();
|
||||||
}
|
}
|
||||||
if (myChart2 != null && myChart2 !== '' && myChart2 !== undefined) {
|
if (myChart2 != null && myChart2 !== "" && myChart2 !== undefined) {
|
||||||
myChart2.dispose()
|
myChart2.dispose();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const buildCharts = () => {
|
const buildCharts = () => {
|
||||||
disposeCharts()
|
disposeCharts();
|
||||||
|
|
||||||
myChart1 = echarts.init(document.getElementById('multi-kills-chart-1'), {}, {
|
myChart1 = echarts.init(
|
||||||
width: width.value,
|
document.getElementById("multi-kills-chart-1"),
|
||||||
height: height.value
|
{},
|
||||||
});
|
{
|
||||||
|
width: width.value,
|
||||||
|
height: height.value,
|
||||||
|
}
|
||||||
|
);
|
||||||
myChart1.setOption(optionGen(1));
|
myChart1.setOption(optionGen(1));
|
||||||
|
|
||||||
myChart2 = echarts.init(document.getElementById('multi-kills-chart-2'), {}, {
|
myChart2 = echarts.init(
|
||||||
width: width.value,
|
document.getElementById("multi-kills-chart-2"),
|
||||||
height: height.value
|
{},
|
||||||
});
|
{
|
||||||
|
width: width.value,
|
||||||
|
height: height.value,
|
||||||
|
}
|
||||||
|
);
|
||||||
myChart2.setOption(optionGen(2));
|
myChart2.setOption(optionGen(2));
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (store.state.matchDetails.stats) {
|
if (store.state.matchDetails.stats) {
|
||||||
@@ -148,27 +185,27 @@ export default {
|
|||||||
GridComponent,
|
GridComponent,
|
||||||
VisualMapComponent,
|
VisualMapComponent,
|
||||||
HeatmapChart,
|
HeatmapChart,
|
||||||
CanvasRenderer
|
CanvasRenderer,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
buildCharts()
|
buildCharts();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
disposeCharts()
|
disposeCharts();
|
||||||
})
|
});
|
||||||
|
|
||||||
window.onresize = () => {
|
window.onresize = () => {
|
||||||
if (window.innerWidth <= 500) {
|
if (window.innerWidth <= 500) {
|
||||||
width.value = window.innerWidth - 20
|
width.value = window.innerWidth - 20;
|
||||||
height.value = width.value
|
height.value = width.value;
|
||||||
|
|
||||||
buildCharts()
|
buildCharts();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -1,360 +0,0 @@
|
|||||||
<template>
|
|
||||||
<nav class="navbar navbar-expand-md navbar-dark fixed-top">
|
|
||||||
<div class="container">
|
|
||||||
<router-link class="navbar-brand" to="/" @click="closeNav('mainNav')">
|
|
||||||
<img alt="logo-nav"
|
|
||||||
class="logo-nav"
|
|
||||||
src="/images/logo.svg">
|
|
||||||
</router-link>
|
|
||||||
<button aria-controls="mainNav" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler"
|
|
||||||
data-bs-target="#mainNav" data-bs-toggle="collapse" type="button">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div id="mainNav" class="collapse navbar-collapse navbar-nav justify-content-between">
|
|
||||||
<ul class="list-unstyled">
|
|
||||||
<li class="nav-item">
|
|
||||||
<router-link class="nav-link" to="/matches" @click="closeNav('mainNav')">
|
|
||||||
Matches
|
|
||||||
</router-link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<form id="searchform" class="d-flex" @keydown.enter.prevent="parseSearch" @submit.prevent="parseSearch">
|
|
||||||
<label for="search">
|
|
||||||
<i class="fa fa-search"></i>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<input id="search" v-model="data.searchInput" aria-label="Search"
|
|
||||||
autocomplete="off"
|
|
||||||
class="form-control bg-transparent border-0"
|
|
||||||
placeholder="SteamID64, Profile Link or Custom URL"
|
|
||||||
title="SteamID64, Profile Link or Custom URL"
|
|
||||||
type="search">
|
|
||||||
<button
|
|
||||||
id="search-button"
|
|
||||||
class="btn border-2 btn-outline-info"
|
|
||||||
type="button"
|
|
||||||
@click="parseSearch"
|
|
||||||
>
|
|
||||||
Search!
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {reactive} from "vue";
|
|
||||||
import {useStore} from 'vuex'
|
|
||||||
import {closeNav, GetUser, GoToPlayer} from '@/utils'
|
|
||||||
import {StatusCodes as STATUS} from "http-status-codes";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Nav',
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
const data = reactive({
|
|
||||||
searchInput: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
const parseSearch = async () => {
|
|
||||||
const input = data.searchInput
|
|
||||||
const customUrlPattern = 'https://steamcommunity.com/id/'
|
|
||||||
const profileUrlPattern = 'https://steamcommunity.com/profiles/'
|
|
||||||
const id64Pattern = /^\d{17}$/
|
|
||||||
const vanityPattern = /^[A-Za-z0-9-_]{3,32}$/
|
|
||||||
|
|
||||||
store.commit({
|
|
||||||
type: 'changeVanityUrl',
|
|
||||||
id: ''
|
|
||||||
})
|
|
||||||
store.commit({
|
|
||||||
type: 'changeId64',
|
|
||||||
id: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
if (data.searchInput !== '') {
|
|
||||||
if (id64Pattern.test(input)) {
|
|
||||||
store.commit({
|
|
||||||
type: 'changeId64',
|
|
||||||
id: input
|
|
||||||
})
|
|
||||||
} else if (input.match(customUrlPattern)) {
|
|
||||||
store.commit({
|
|
||||||
type: 'changeVanityUrl',
|
|
||||||
id: input.split('/')[4].split('?')[0]
|
|
||||||
})
|
|
||||||
} else if (input.match(profileUrlPattern)) {
|
|
||||||
const tmp = input.split('/')[4].split('?')[0]
|
|
||||||
if (id64Pattern.test(tmp)) {
|
|
||||||
store.commit({
|
|
||||||
type: 'changeId64',
|
|
||||||
id: tmp
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
store.commit({
|
|
||||||
type: 'changeVanityUrl',
|
|
||||||
id: input
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (store.state.vanityUrl && !vanityPattern.test(store.state.vanityUrl)) {
|
|
||||||
store.commit({
|
|
||||||
type: 'changeInfoState',
|
|
||||||
data: {
|
|
||||||
statuscode: STATUS.NOT_ACCEPTABLE,
|
|
||||||
message: 'Only alphanumeric symbols, "_", and "-", between 3-32 characters',
|
|
||||||
type: 'warning'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
store.commit({
|
|
||||||
type: 'changeVanityUrl',
|
|
||||||
id: ''
|
|
||||||
})
|
|
||||||
data.searchInput = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if (store.state.id64 !== '' || store.state.vanityUrl !== '') {
|
|
||||||
const resData = await GetUser(store, store.state.vanityUrl || store.state.id64)
|
|
||||||
|
|
||||||
if (resData !== null) {
|
|
||||||
data.searchInput = ''
|
|
||||||
document.activeElement.blur()
|
|
||||||
|
|
||||||
store.commit({
|
|
||||||
type: 'changePlayerDetails',
|
|
||||||
data: resData
|
|
||||||
})
|
|
||||||
|
|
||||||
if (store.state.vanityUrl) {
|
|
||||||
closeNav('mainNav')
|
|
||||||
GoToPlayer(store.state.vanityUrl)
|
|
||||||
} else if (store.state.id64) {
|
|
||||||
closeNav('mainNav')
|
|
||||||
GoToPlayer(store.state.id64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('click', (e) => {
|
|
||||||
if (!e.target.attributes.id)
|
|
||||||
closeNav('mainNav')
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
data, parseSearch, closeNav
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.navbar-dark .navbar-brand:hover,
|
|
||||||
.navbar-dark .navbar-brand:focus {
|
|
||||||
color: var(--bs-warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
max-width: 100vw;
|
|
||||||
width: 100vw;
|
|
||||||
height: 70px;
|
|
||||||
background: rgba(16, 18, 26, .9);
|
|
||||||
box-shadow: 0 1px 10px 0 #111;
|
|
||||||
z-index: 2;
|
|
||||||
vertical-align: center !important;
|
|
||||||
|
|
||||||
.navbar-brand {
|
|
||||||
img {
|
|
||||||
width: 75px;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus-visible {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 0 4px 2px -2px var(--bs-warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--bs-warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul li {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: lighter;
|
|
||||||
margin: 22px 0 0 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: 100ms ease-in-out;
|
|
||||||
|
|
||||||
.nav-link {
|
|
||||||
text-decoration: none;
|
|
||||||
color: white !important;
|
|
||||||
|
|
||||||
.router-link-exact-active {
|
|
||||||
box-shadow: 0 4px 2px -2px var(--bs-warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus-visible {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 0 4px 2px -2px var(--bs-warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #bdbdbd !important;
|
|
||||||
transition: 250ms ease-in-out;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 4px 2px -2px var(--bs-warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
fill: currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
padding-top: 6px;
|
|
||||||
font-size: 1.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
font-size: .9rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 55px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 410px) {
|
|
||||||
form {
|
|
||||||
margin-left: auto !important;
|
|
||||||
margin-right: auto !important;
|
|
||||||
|
|
||||||
input[type="search"] {
|
|
||||||
margin-left: 0 !important;
|
|
||||||
max-width: 60vw !important;
|
|
||||||
min-width: 60vw !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 455px) and (min-width: 410px) {
|
|
||||||
form {
|
|
||||||
margin-left: auto !important;
|
|
||||||
margin-right: auto !important;
|
|
||||||
|
|
||||||
input[type="search"] {
|
|
||||||
margin-left: 0 !important;
|
|
||||||
max-width: 65vw !important;
|
|
||||||
min-width: 65vw !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 610px) and (min-width: 456px) {
|
|
||||||
form {
|
|
||||||
margin-left: auto !important;
|
|
||||||
margin-right: auto !important;
|
|
||||||
|
|
||||||
input[type="search"] {
|
|
||||||
margin-left: 0 !important;
|
|
||||||
max-width: 68vw !important;
|
|
||||||
min-width: 68vw !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
nav {
|
|
||||||
button {
|
|
||||||
outline: 1px solid var(--bs-primary);
|
|
||||||
margin-left: auto;
|
|
||||||
float: right;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
box-shadow: none;
|
|
||||||
outline: 1px solid var(--bs-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-collapse {
|
|
||||||
background: var(--bs-secondary);
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 1px solid var(--bs-primary)
|
|
||||||
}
|
|
||||||
|
|
||||||
#mainNav {
|
|
||||||
ul {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
li {
|
|
||||||
line-height: 1;
|
|
||||||
padding: 0 0 20px 0;
|
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, .1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
max-width: 87vw;
|
|
||||||
margin-left: -40px;
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="search"] {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
margin-left: 37px;
|
|
||||||
max-width: 400px;
|
|
||||||
min-width: 400px;
|
|
||||||
font-size: 1rem;
|
|
||||||
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
background: var(--bs-body-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin-left: 10px;
|
|
||||||
display: block;
|
|
||||||
margin-top: -2px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
405
src/components/NavComponent.vue
Normal file
405
src/components/NavComponent.vue
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
<template>
|
||||||
|
<nav class="navbar navbar-expand-md navbar-dark fixed-top">
|
||||||
|
<div class="container">
|
||||||
|
<router-link class="navbar-brand" to="/" @click="closeNav('mainNav')">
|
||||||
|
<img alt="logo-nav" class="logo-nav" src="/images/logo.svg" />
|
||||||
|
</router-link>
|
||||||
|
<button
|
||||||
|
aria-controls="mainNav"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-label="Toggle navigation"
|
||||||
|
class="navbar-toggler"
|
||||||
|
data-bs-target="#mainNav"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="mainNav"
|
||||||
|
class="collapse navbar-collapse navbar-nav justify-content-between"
|
||||||
|
>
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li class="nav-item">
|
||||||
|
<router-link
|
||||||
|
class="nav-link"
|
||||||
|
to="/matches"
|
||||||
|
@click="closeNav('mainNav')"
|
||||||
|
>
|
||||||
|
Matches
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<form
|
||||||
|
id="search-form"
|
||||||
|
class="d-flex"
|
||||||
|
@keydown.enter.prevent="parseSearch"
|
||||||
|
@submit.prevent="parseSearch"
|
||||||
|
>
|
||||||
|
<label for="search">
|
||||||
|
<i class="fa fa-search"></i>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
id="search"
|
||||||
|
v-model="data.searchInput"
|
||||||
|
aria-label="Search"
|
||||||
|
autocomplete="off"
|
||||||
|
class="form-control bg-transparent border-0"
|
||||||
|
placeholder="SteamID, Profile Link or ShareCode"
|
||||||
|
title="SteamID, Profile Link or ShareCode"
|
||||||
|
type="search"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
id="search-button"
|
||||||
|
class="btn border-2 btn-outline-info"
|
||||||
|
type="button"
|
||||||
|
@click="parseSearch"
|
||||||
|
>
|
||||||
|
Search!
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive } from "vue";
|
||||||
|
import {
|
||||||
|
closeNav,
|
||||||
|
closeNavEventListener,
|
||||||
|
GetMatchDetails,
|
||||||
|
GetUser,
|
||||||
|
GoToMatch,
|
||||||
|
GoToPlayer,
|
||||||
|
ParseMatch,
|
||||||
|
parseShareCode,
|
||||||
|
sleep,
|
||||||
|
stringSanitizer,
|
||||||
|
} from "@/utils";
|
||||||
|
import {
|
||||||
|
CUSTOM_URL_BASE,
|
||||||
|
ID64_PATTERN,
|
||||||
|
MATCH_SHARE_URL_BASE,
|
||||||
|
PROFILE_URL_BASE,
|
||||||
|
SHARECODE_REGEX,
|
||||||
|
VANITY_PATTERN,
|
||||||
|
} from "@/constants";
|
||||||
|
import { StatusCodes as STATUS } from "http-status-codes";
|
||||||
|
import { useSearchParamsStore } from "@/stores/searchParams";
|
||||||
|
import type { infoState } from "@/stores/infoState";
|
||||||
|
import { useInfoStateStore } from "@/stores/infoState";
|
||||||
|
import { usePlayerDetailsStore } from "@/stores/playerDetails";
|
||||||
|
|
||||||
|
const searchParamsStore = useSearchParamsStore();
|
||||||
|
const infoStateStore = useInfoStateStore();
|
||||||
|
const playerDetailsStore = usePlayerDetailsStore();
|
||||||
|
|
||||||
|
const data = reactive({
|
||||||
|
searchInput: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const parseSearch = async () => {
|
||||||
|
let input = data.searchInput;
|
||||||
|
|
||||||
|
searchParamsStore.$reset();
|
||||||
|
|
||||||
|
if (data.searchInput !== "") {
|
||||||
|
// remove various base-urls + cut excess parameters
|
||||||
|
input = input
|
||||||
|
.replace(MATCH_SHARE_URL_BASE, "")
|
||||||
|
.replace(CUSTOM_URL_BASE, "")
|
||||||
|
.replace(PROFILE_URL_BASE, "")
|
||||||
|
.split("/")[0]
|
||||||
|
.split("?")[0];
|
||||||
|
|
||||||
|
// process shareCode
|
||||||
|
const tmpShareCode = Array.from(input.matchAll(SHARECODE_REGEX));
|
||||||
|
const inputShareCode = tmpShareCode.length > 0 ? tmpShareCode[0][0] : "";
|
||||||
|
searchParamsStore.shareCode = SHARECODE_REGEX.test(inputShareCode)
|
||||||
|
? inputShareCode
|
||||||
|
: "";
|
||||||
|
|
||||||
|
// process id64
|
||||||
|
const tmpId64 = Array.from(input.matchAll(ID64_PATTERN));
|
||||||
|
const inputId64 = tmpId64.length > 0 ? tmpId64[0][0] : "";
|
||||||
|
searchParamsStore.id64 = ID64_PATTERN.test(inputId64) ? inputId64 : "";
|
||||||
|
|
||||||
|
// process vanityUrl
|
||||||
|
if (searchParamsStore.shareCode === "" && searchParamsStore.id64 === "") {
|
||||||
|
if (input.includes(CUSTOM_URL_BASE)) {
|
||||||
|
input = input.replace(CUSTOM_URL_BASE, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VANITY_PATTERN.test(input)) {
|
||||||
|
searchParamsStore.vanityUrl = stringSanitizer(input);
|
||||||
|
} else {
|
||||||
|
const info: infoState = {
|
||||||
|
statusCode: STATUS.NOT_ACCEPTABLE,
|
||||||
|
message:
|
||||||
|
'Only alphanumeric symbols, "_", and "-", between 3-32 characters',
|
||||||
|
type: "warning",
|
||||||
|
};
|
||||||
|
infoStateStore.addInfo(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUser
|
||||||
|
if (searchParamsStore.id64 !== "" || searchParamsStore.vanityUrl !== "") {
|
||||||
|
const [resData, info] = await GetUser(
|
||||||
|
searchParamsStore.vanityUrl || searchParamsStore.id64
|
||||||
|
);
|
||||||
|
|
||||||
|
if (info.message !== "") infoStateStore.addInfo(info);
|
||||||
|
if (resData !== null) {
|
||||||
|
data.searchInput = "";
|
||||||
|
const activeElem = document.activeElement as HTMLInputElement;
|
||||||
|
activeElem.blur();
|
||||||
|
|
||||||
|
playerDetailsStore.playerDetails = resData;
|
||||||
|
|
||||||
|
if (searchParamsStore.vanityUrl) {
|
||||||
|
closeNav("mainNav");
|
||||||
|
GoToPlayer(searchParamsStore.vanityUrl);
|
||||||
|
} else if (searchParamsStore.id64) {
|
||||||
|
closeNav("mainNav");
|
||||||
|
GoToPlayer(searchParamsStore.id64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseMatch
|
||||||
|
if (searchParamsStore.shareCode !== "") {
|
||||||
|
data.searchInput = "";
|
||||||
|
|
||||||
|
const matchId = parseShareCode(searchParamsStore.shareCode);
|
||||||
|
let info = await ParseMatch(searchParamsStore.shareCode);
|
||||||
|
|
||||||
|
if (info.message !== "") infoStateStore.addInfo(info);
|
||||||
|
if (info.statusCode === STATUS.OK) GoToMatch(matchId);
|
||||||
|
|
||||||
|
if (info.statusCode === STATUS.ACCEPTED) {
|
||||||
|
let [res, info] = await GetMatchDetails(matchId);
|
||||||
|
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
if (res !== null && res.parsed) break;
|
||||||
|
[res, info] = await GetMatchDetails(matchId);
|
||||||
|
|
||||||
|
sleep(2000);
|
||||||
|
}
|
||||||
|
GoToMatch(matchId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
closeNavEventListener("mainNav");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.navbar-dark .navbar-brand:hover,
|
||||||
|
.navbar-dark .navbar-brand:focus {
|
||||||
|
color: var(--bs-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
max-width: 100vw;
|
||||||
|
width: 100vw;
|
||||||
|
height: 70px;
|
||||||
|
background: rgba(16, 18, 26, 0.9);
|
||||||
|
box-shadow: 0 1px 10px 0 #111;
|
||||||
|
z-index: 2;
|
||||||
|
vertical-align: center !important;
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
img {
|
||||||
|
width: 75px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 4px 2px -2px var(--bs-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--bs-warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: lighter;
|
||||||
|
margin: 22px 0 0 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 100ms ease-in-out;
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
text-decoration: none;
|
||||||
|
color: white !important;
|
||||||
|
|
||||||
|
.router-link-exact-active {
|
||||||
|
box-shadow: 0 4px 2px -2px var(--bs-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 4px 2px -2px var(--bs-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #bdbdbd !important;
|
||||||
|
transition: 250ms ease-in-out;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 4px 2px -2px var(--bs-warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
padding-top: 6px;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="search"] {
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 300px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0 4px 2px -2px rgba(95, 120, 146, 0.59);
|
||||||
|
transition: 0.2s ease-in-out;
|
||||||
|
transform: scale(0.975);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 55px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 410px) {
|
||||||
|
form {
|
||||||
|
margin-left: auto !important;
|
||||||
|
margin-right: auto !important;
|
||||||
|
|
||||||
|
input[type="search"] {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
max-width: 60vw !important;
|
||||||
|
min-width: 60vw !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 455px) and (min-width: 410px) {
|
||||||
|
form {
|
||||||
|
margin-left: auto !important;
|
||||||
|
margin-right: auto !important;
|
||||||
|
|
||||||
|
input[type="search"] {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
max-width: 65vw !important;
|
||||||
|
min-width: 65vw !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 610px) and (min-width: 456px) {
|
||||||
|
form {
|
||||||
|
margin-left: auto !important;
|
||||||
|
margin-right: auto !important;
|
||||||
|
|
||||||
|
input[type="search"] {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
max-width: 68vw !important;
|
||||||
|
min-width: 68vw !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
nav {
|
||||||
|
button {
|
||||||
|
outline: 1px solid var(--bs-primary);
|
||||||
|
margin-left: auto;
|
||||||
|
float: right;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
outline: 1px solid var(--bs-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-collapse {
|
||||||
|
background: var(--bs-secondary);
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid var(--bs-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#mainNav {
|
||||||
|
ul {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
li {
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0 0 20px 0;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
max-width: 87vw;
|
||||||
|
margin-left: -40px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="search"] {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
margin-left: 37px;
|
||||||
|
max-width: 400px;
|
||||||
|
min-width: 400px;
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
background: var(--bs-body-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-left: 10px;
|
||||||
|
display: block;
|
||||||
|
margin-top: -2px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,16 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="side-info">
|
<div class="side-info">
|
||||||
|
<div
|
||||||
<div v-if="props.player_meta.most_mates" class="side-info-box most-played-with">
|
v-if="props.player_meta.most_mates"
|
||||||
|
class="side-info-box most-played-with"
|
||||||
|
>
|
||||||
<div class="heading">
|
<div class="heading">
|
||||||
<h5>Most played with</h5>
|
<h5>Most played with</h5>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr />
|
||||||
<ul v-for="mate in props.player_meta.most_mates" :key="mate.player.steamid64" class="list-unstyled">
|
<ul
|
||||||
<li @click="GoToPlayer(mate.player.vanity_url || mate.player.steamid64)">
|
v-for="mate in props.player_meta.most_mates"
|
||||||
|
:key="mate.player.steamid64"
|
||||||
|
class="list-unstyled"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
@click="GoToPlayer(mate.player.vanity_url || mate.player.steamid64)"
|
||||||
|
>
|
||||||
<span class="start">
|
<span class="start">
|
||||||
<img :class="mate.player.tracked ? 'tracked' : ''" :src="constructAvatarUrl(mate.player.avatar)"
|
<img
|
||||||
:title="mate.player.tracked ? 'Tracked' : ''" alt="Player avatar">
|
:class="mate.player.tracked ? 'tracked' : ''"
|
||||||
|
:src="constructAvatarUrl(mate.player.avatar)"
|
||||||
|
:title="mate.player.tracked ? 'Tracked' : ''"
|
||||||
|
alt="Player avatar"
|
||||||
|
/>
|
||||||
<span class="text">{{ mate.player.name }}</span>
|
<span class="text">{{ mate.player.name }}</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="end">
|
<span class="end">
|
||||||
@@ -24,7 +36,7 @@
|
|||||||
<div class="heading">
|
<div class="heading">
|
||||||
<h5>Most played with</h5>
|
<h5>Most played with</h5>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr />
|
||||||
<ul class="list-unstyled placeholder-glow">
|
<ul class="list-unstyled placeholder-glow">
|
||||||
<li class="placeholder col-11"></li>
|
<li class="placeholder col-11"></li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -34,17 +46,29 @@
|
|||||||
<div class="heading">
|
<div class="heading">
|
||||||
<h5>Best Mate <span class="text-muted">(by winrate)</span></h5>
|
<h5>Best Mate <span class="text-muted">(by winrate)</span></h5>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr />
|
||||||
<ul v-for="mate in props.player_meta.best_mates" :key="mate.player.steamid64" class="list-unstyled">
|
<ul
|
||||||
<li @click="GoToPlayer(mate.player.vanity_url || mate.player.steamid64)">
|
v-for="mate in props.player_meta.best_mates"
|
||||||
|
:key="mate.player.steamid64"
|
||||||
|
class="list-unstyled"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
@click="GoToPlayer(mate.player.vanity_url || mate.player.steamid64)"
|
||||||
|
>
|
||||||
<span class="start">
|
<span class="start">
|
||||||
<img :class="mate.player.tracked ? 'tracked' : ''" :src="constructAvatarUrl(mate.player.avatar)"
|
<img
|
||||||
:title="mate.player.tracked ? 'Tracked' : ''" alt="Player avatar">
|
:class="mate.player.tracked ? 'tracked' : ''"
|
||||||
|
:src="constructAvatarUrl(mate.player.avatar)"
|
||||||
|
:title="mate.player.tracked ? 'Tracked' : ''"
|
||||||
|
alt="Player avatar"
|
||||||
|
/>
|
||||||
<span class="text">{{ mate.player.name }}</span>
|
<span class="text">{{ mate.player.name }}</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="end">
|
<span class="end">
|
||||||
{{ mate.win_rate ? (mate.win_rate * 100).toFixed(0) : 0 }} %
|
{{ mate.win_rate ? (mate.win_rate * 100).toFixed(0) : 0 }} %
|
||||||
<span v-if="mate.total" class="total text-muted">({{ mate.total }})</span>
|
<span v-if="mate.total" class="total text-muted"
|
||||||
|
>({{ mate.total }})</span
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -54,18 +78,25 @@
|
|||||||
<div class="heading">
|
<div class="heading">
|
||||||
<h5>Best Mate <span class="text-muted">(by winrate)</span></h5>
|
<h5>Best Mate <span class="text-muted">(by winrate)</span></h5>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr />
|
||||||
<ul class="list-unstyled placeholder-glow">
|
<ul class="list-unstyled placeholder-glow">
|
||||||
<li class="placeholder col-11"></li>
|
<li class="placeholder col-11"></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="props.player_meta.eq_map && props.player_meta.weapon_dmg" class="side-info-box preferred-weapons">
|
<div
|
||||||
|
v-if="props.player_meta.eq_map && props.player_meta.weapon_dmg"
|
||||||
|
class="side-info-box preferred-weapons"
|
||||||
|
>
|
||||||
<div class="heading">
|
<div class="heading">
|
||||||
<h5>Weapons <span class="text-muted">(by dmg)</span></h5>
|
<h5>Weapons <span class="text-muted">(by dmg)</span></h5>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr />
|
||||||
<ul v-for="(id, key) in data.best_weapons" :key="id[0]" class="list-unstyled">
|
<ul
|
||||||
|
v-for="(id, key) in data.best_weapons"
|
||||||
|
:key="id[0]"
|
||||||
|
class="list-unstyled"
|
||||||
|
>
|
||||||
<li>
|
<li>
|
||||||
<span class="start">
|
<span class="start">
|
||||||
<span class="text">{{ id[0] }}</span>
|
<span class="text">{{ id[0] }}</span>
|
||||||
@@ -84,7 +115,7 @@
|
|||||||
<div class="heading">
|
<div class="heading">
|
||||||
<h5>Weapons <span class="text-muted">(by dmg)</span></h5>
|
<h5>Weapons <span class="text-muted">(by dmg)</span></h5>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr />
|
||||||
<ul class="list-unstyled placeholder-glow">
|
<ul class="list-unstyled placeholder-glow">
|
||||||
<li class="placeholder col-11"></li>
|
<li class="placeholder col-11"></li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -94,17 +125,23 @@
|
|||||||
<div class="heading">
|
<div class="heading">
|
||||||
<h5>Best Map <span class="text-muted">(by winrate)</span></h5>
|
<h5>Best Map <span class="text-muted">(by winrate)</span></h5>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr />
|
||||||
<ul v-for="map in data.best_maps" :key="map[0]" class="list-unstyled">
|
<ul v-for="map in data.best_maps" :key="map[0]" class="list-unstyled">
|
||||||
<li>
|
<li>
|
||||||
<span class="start">
|
<span class="start">
|
||||||
<img :src="'/images/map_icons/map_icon_' + map[0] + '.svg'" alt="Player avatar">
|
<img
|
||||||
|
:src="'/images/map_icons/map_icon_' + map[0] + '.svg'"
|
||||||
|
alt="Player avatar"
|
||||||
|
/>
|
||||||
<span class="text">{{ FixMapName(map[0]) }}</span>
|
<span class="text">{{ FixMapName(map[0]) }}</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="end">
|
<span class="end">
|
||||||
{{ (map[1] * 100).toFixed(0) }} %
|
{{ (map[1] * 100).toFixed(0) }} %
|
||||||
<span v-if="props.player_meta.total_maps[map[0]]"
|
<span
|
||||||
class="total text-muted">({{ props.player_meta.total_maps[map[0]] }})</span>
|
v-if="props.player_meta.total_maps[map[0]]"
|
||||||
|
class="total text-muted"
|
||||||
|
>({{ props.player_meta.total_maps[map[0]] }})</span
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -114,7 +151,7 @@
|
|||||||
<div class="heading">
|
<div class="heading">
|
||||||
<h5>Best Map <span class="text-muted">(by winrate)</span></h5>
|
<h5>Best Map <span class="text-muted">(by winrate)</span></h5>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr />
|
||||||
<ul class="list-unstyled placeholder-glow">
|
<ul class="list-unstyled placeholder-glow">
|
||||||
<li class="placeholder col-11"></li>
|
<li class="placeholder col-11"></li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -123,97 +160,111 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import {
|
||||||
import {constructAvatarUrl, FixMapName, GoToPlayer, sortObjectValue} from "@/utils";
|
constructAvatarUrl,
|
||||||
import {reactive, ref, watch} from "vue";
|
FixMapName,
|
||||||
|
GoToPlayer,
|
||||||
|
sortObjectValue,
|
||||||
|
} from "/src/utils";
|
||||||
|
import { reactive, ref, watch } from "vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PlayerSideInfo",
|
name: "PlayerSideInfo",
|
||||||
props: {
|
props: {
|
||||||
player_meta: {
|
player_meta: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const displayCounter = 3
|
const displayCounter = 3;
|
||||||
|
|
||||||
const mostMatesLoading = ref(true)
|
const mostMatesLoading = ref(true);
|
||||||
const bestMatesLoading = ref(true)
|
const bestMatesLoading = ref(true);
|
||||||
const weaponsLoading = ref(true)
|
const weaponsLoading = ref(true);
|
||||||
const mapsLoading = ref(true)
|
const mapsLoading = ref(true);
|
||||||
|
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
best_maps: [],
|
best_maps: [],
|
||||||
best_weapons_tmp: [],
|
best_weapons_tmp: [],
|
||||||
best_weapons: []
|
best_weapons: [],
|
||||||
})
|
});
|
||||||
|
|
||||||
const mapWeaponDamage = () => {
|
const mapWeaponDamage = () => {
|
||||||
if (props.player_meta.eq_map && props.player_meta.weapon_dmg) {
|
if (props.player_meta.eq_map && props.player_meta.weapon_dmg) {
|
||||||
Object.keys(props.player_meta.eq_map).forEach((key) => {
|
Object.keys(props.player_meta.eq_map).forEach((key) => {
|
||||||
for (const id in props.player_meta.weapon_dmg) {
|
for (const id in props.player_meta.weapon_dmg) {
|
||||||
Object.keys(props.player_meta.weapon_dmg[id]).forEach((k) => {
|
Object.keys(props.player_meta.weapon_dmg[id]).forEach((k) => {
|
||||||
if (k === 'eq') {
|
if (k === "eq") {
|
||||||
if (props.player_meta.weapon_dmg[id][k] === key * 1) {
|
if (props.player_meta.weapon_dmg[id][k] === key * 1) {
|
||||||
data.best_weapons_tmp.push([props.player_meta.eq_map[key], props.player_meta.weapon_dmg[id]['dmg']])
|
data.best_weapons_tmp.push([
|
||||||
|
props.player_meta.eq_map[key],
|
||||||
|
props.player_meta.weapon_dmg[id]["dmg"],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
data.best_weapons_tmp.sort((a, b) => {
|
data.best_weapons_tmp.sort((a, b) => {
|
||||||
return b[1] - a[1]
|
return b[1] - a[1];
|
||||||
})
|
});
|
||||||
|
|
||||||
data.best_weapons = data.best_weapons_tmp
|
data.best_weapons = data.best_weapons_tmp;
|
||||||
data.best_weapons_tmp = []
|
data.best_weapons_tmp = [];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const setDmgGraphWidth = () => {
|
const setDmgGraphWidth = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
let weaponsContainer
|
let weaponsContainer;
|
||||||
const dmg100 = ref(0)
|
const dmg100 = ref(0);
|
||||||
const dmg = ref(0)
|
const dmg = ref(0);
|
||||||
|
|
||||||
for (let i = 0; i <= 4; i++) {
|
for (let i = 0; i <= 4; i++) {
|
||||||
weaponsContainer = document.querySelector('.dmg-chart-' + i)
|
weaponsContainer = document.querySelector(".dmg-chart-" + i);
|
||||||
if (weaponsContainer !== null) {
|
if (weaponsContainer !== null) {
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
dmg100.value = weaponsContainer.innerHTML * 1
|
dmg100.value = weaponsContainer.innerHTML * 1;
|
||||||
weaponsContainer.style.width = '100%'
|
weaponsContainer.style.width = "100%";
|
||||||
}
|
}
|
||||||
|
|
||||||
dmg.value = weaponsContainer.innerHTML * 1
|
dmg.value = weaponsContainer.innerHTML * 1;
|
||||||
weaponsContainer.style.width = dmg.value * 100 / dmg100.value + '%'
|
weaponsContainer.style.width =
|
||||||
|
(dmg.value * 100) / dmg100.value + "%";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 100)
|
}, 100);
|
||||||
}
|
};
|
||||||
|
|
||||||
watch(() => props.player_meta, () => {
|
watch(
|
||||||
mapWeaponDamage()
|
() => props.player_meta,
|
||||||
|
() => {
|
||||||
|
mapWeaponDamage();
|
||||||
|
|
||||||
data.best_maps = sortObjectValue(props.player_meta.win_maps, 'desc')
|
data.best_maps = sortObjectValue(props.player_meta.win_maps, "desc");
|
||||||
|
|
||||||
if (data.best_maps.length > displayCounter)
|
if (data.best_maps.length > displayCounter)
|
||||||
data.best_maps.splice(displayCounter, data.best_maps.length - displayCounter)
|
data.best_maps.splice(
|
||||||
|
displayCounter,
|
||||||
|
data.best_maps.length - displayCounter
|
||||||
|
);
|
||||||
|
|
||||||
if (!props.player_meta.most_mates) {
|
if (!props.player_meta.most_mates) {
|
||||||
mostMatesLoading.value = false
|
mostMatesLoading.value = false;
|
||||||
|
}
|
||||||
|
if (!props.player_meta.best_mates) {
|
||||||
|
bestMatesLoading.value = false;
|
||||||
|
}
|
||||||
|
if (!props.player_meta.win_maps) {
|
||||||
|
mapsLoading.value = false;
|
||||||
|
}
|
||||||
|
if (!props.player_meta.eq_map || !props.player_meta.weapon_dmg) {
|
||||||
|
weaponsLoading.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!props.player_meta.best_mates) {
|
);
|
||||||
bestMatesLoading.value = false
|
|
||||||
}
|
|
||||||
if (!props.player_meta.win_maps) {
|
|
||||||
mapsLoading.value = false
|
|
||||||
}
|
|
||||||
if (!props.player_meta.eq_map || !props.player_meta.weapon_dmg) {
|
|
||||||
weaponsLoading.value = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props,
|
props,
|
||||||
@@ -225,10 +276,10 @@ export default {
|
|||||||
setDmgGraphWidth,
|
setDmgGraphWidth,
|
||||||
GoToPlayer,
|
GoToPlayer,
|
||||||
constructAvatarUrl,
|
constructAvatarUrl,
|
||||||
FixMapName
|
FixMapName,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -251,12 +302,14 @@ export default {
|
|||||||
.side-info-box {
|
.side-info-box {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
background: rgba(20, 20, 20, .8);
|
background: rgba(20, 20, 20, 0.8);
|
||||||
border: 1px solid rgba(white, .3);
|
border: 1px solid rgba(white, 0.3);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ol, ul, dl {
|
ol,
|
||||||
|
ul,
|
||||||
|
dl {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,12 +333,12 @@ export default {
|
|||||||
|
|
||||||
hr {
|
hr {
|
||||||
margin: 0 0 5px 0;
|
margin: 0 0 5px 0;
|
||||||
border-color: rgba(white, .3);
|
border-color: rgba(white, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
ul li {
|
ul li {
|
||||||
line-height: 25px;
|
line-height: 25px;
|
||||||
font-size: .9rem;
|
font-size: 0.9rem;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -302,7 +355,7 @@ export default {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
.tracked {
|
.tracked {
|
||||||
font-size: .8rem;
|
font-size: 0.8rem;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,7 +381,8 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.best-map, .best-mate {
|
.best-map,
|
||||||
|
.best-mate {
|
||||||
ul li {
|
ul li {
|
||||||
.start {
|
.start {
|
||||||
width: 75%;
|
width: 75%;
|
||||||
|
|||||||
@@ -5,187 +5,268 @@
|
|||||||
<div v-if="store.state.matchDetails.max_rounds === 16" id="short-match">
|
<div v-if="store.state.matchDetails.max_rounds === 16" id="short-match">
|
||||||
<div class="team-1">
|
<div class="team-1">
|
||||||
<div class="score-text">
|
<div class="score-text">
|
||||||
<span v-if="store.state.matchDetails.score[0] < 10"
|
<span
|
||||||
:style="store.state.matchDetails.score[0] < 10 ? 'margin-left: -10px;' : ''"
|
v-if="store.state.matchDetails.score[0] < 10"
|
||||||
class="hidden">0</span><span
|
:style="
|
||||||
:class="store.state.matchDetails.score[0] === 9 ? 'text-success' : store.state.matchDetails.score[0] === 8 ? 'text-warning' : 'text-danger'">{{
|
store.state.matchDetails.score[0] < 10
|
||||||
store.state.matchDetails.score[0]
|
? 'margin-left: -10px;'
|
||||||
}}</span>
|
: ''
|
||||||
|
"
|
||||||
|
class="hidden"
|
||||||
|
>0</span
|
||||||
|
><span
|
||||||
|
:class="
|
||||||
|
store.state.matchDetails.score[0] === 9
|
||||||
|
? 'text-success'
|
||||||
|
: store.state.matchDetails.score[0] === 8
|
||||||
|
? 'text-warning'
|
||||||
|
: 'text-danger'
|
||||||
|
"
|
||||||
|
>{{ store.state.matchDetails.score[0] }}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<img alt="CT logo" src="/images/icons/ct_logo.svg">
|
<img alt="CT logo" src="/images/icons/ct_logo.svg" />
|
||||||
<img alt="T logo" src="/images/icons/t_logo.svg">
|
<img alt="T logo" src="/images/icons/t_logo.svg" />
|
||||||
</div>
|
</div>
|
||||||
<div class="team-2">
|
<div class="team-2">
|
||||||
<div class="score-text">
|
<div class="score-text">
|
||||||
<span v-if="store.state.matchDetails.score[1] < 10"
|
<span
|
||||||
:style="store.state.matchDetails.score[1] < 10 ? 'margin-left: -10px;' : ''"
|
v-if="store.state.matchDetails.score[1] < 10"
|
||||||
class="hidden">0</span><span
|
:style="
|
||||||
:class="store.state.matchDetails.score[1] === 9 ? 'text-success' : store.state.matchDetails.score[1] === 8 ? 'text-warning' : 'text-danger'">{{
|
store.state.matchDetails.score[1] < 10
|
||||||
store.state.matchDetails.score[1]
|
? 'margin-left: -10px;'
|
||||||
}}</span>
|
: ''
|
||||||
|
"
|
||||||
|
class="hidden"
|
||||||
|
>0</span
|
||||||
|
><span
|
||||||
|
:class="
|
||||||
|
store.state.matchDetails.score[1] === 9
|
||||||
|
? 'text-success'
|
||||||
|
: store.state.matchDetails.score[1] === 8
|
||||||
|
? 'text-warning'
|
||||||
|
: 'text-danger'
|
||||||
|
"
|
||||||
|
>{{ store.state.matchDetails.score[1] }}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<img alt="T logo" src="/images/icons/t_logo.svg">
|
<img alt="T logo" src="/images/icons/t_logo.svg" />
|
||||||
<img alt="CT logo" src="/images/icons/ct_logo.svg">
|
<img alt="CT logo" src="/images/icons/ct_logo.svg" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="store.state.matchDetails.max_rounds === 30 || !store.state.matchDetails.max_rounds" id="long-match">
|
<div
|
||||||
|
v-if="
|
||||||
|
store.state.matchDetails.max_rounds === 30 ||
|
||||||
|
!store.state.matchDetails.max_rounds
|
||||||
|
"
|
||||||
|
id="long-match"
|
||||||
|
>
|
||||||
<div class="team-1">
|
<div class="team-1">
|
||||||
<div class="score-text">
|
<div class="score-text">
|
||||||
<span v-if="store.state.matchDetails.score[0] < 10"
|
<span
|
||||||
:style="store.state.matchDetails.score[0] < 10 ? 'margin-left: -10px;' : ''"
|
v-if="store.state.matchDetails.score[0] < 10"
|
||||||
class="hidden">0</span><span
|
:style="
|
||||||
:class="store.state.matchDetails.match_result === 1 ? 'text-success' : store.state.matchDetails.match_result === 0 ? 'text-warning' : 'text-danger'">{{
|
store.state.matchDetails.score[0] < 10
|
||||||
store.state.matchDetails.score[0]
|
? 'margin-left: -10px;'
|
||||||
}}</span>
|
: ''
|
||||||
|
"
|
||||||
|
class="hidden"
|
||||||
|
>0</span
|
||||||
|
><span
|
||||||
|
:class="
|
||||||
|
store.state.matchDetails.match_result === 1
|
||||||
|
? 'text-success'
|
||||||
|
: store.state.matchDetails.match_result === 0
|
||||||
|
? 'text-warning'
|
||||||
|
: 'text-danger'
|
||||||
|
"
|
||||||
|
>{{ store.state.matchDetails.score[0] }}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<img alt="CT logo" src="/images/icons/ct_logo.svg">
|
<img alt="CT logo" src="/images/icons/ct_logo.svg" />
|
||||||
<img alt="T logo" src="/images/icons/t_logo.svg">
|
<img alt="T logo" src="/images/icons/t_logo.svg" />
|
||||||
</div>
|
</div>
|
||||||
<div class="team-2">
|
<div class="team-2">
|
||||||
<div class="score-text">
|
<div class="score-text">
|
||||||
<span v-if="store.state.matchDetails.score[1] < 10"
|
<span
|
||||||
:style="store.state.matchDetails.score[1] < 10 ? 'margin-left: -10px;' : ''"
|
v-if="store.state.matchDetails.score[1] < 10"
|
||||||
class="hidden">0</span><span
|
:style="
|
||||||
:class="store.state.matchDetails.match_result === 2 ? 'text-success' : store.state.matchDetails.match_result === 0 ? 'text-warning' : 'text-danger'">{{
|
store.state.matchDetails.score[1] < 10
|
||||||
store.state.matchDetails.score[1]
|
? 'margin-left: -10px;'
|
||||||
}}</span>
|
: ''
|
||||||
|
"
|
||||||
|
class="hidden"
|
||||||
|
>0</span
|
||||||
|
><span
|
||||||
|
:class="
|
||||||
|
store.state.matchDetails.match_result === 2
|
||||||
|
? 'text-success'
|
||||||
|
: store.state.matchDetails.match_result === 0
|
||||||
|
? 'text-warning'
|
||||||
|
: 'text-danger'
|
||||||
|
"
|
||||||
|
>{{ store.state.matchDetails.score[1] }}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<img alt="T logo" src="/images/icons/t_logo.svg">
|
<img alt="T logo" src="/images/icons/t_logo.svg" />
|
||||||
<img alt="CT logo" src="/images/icons/ct_logo.svg">
|
<img alt="CT logo" src="/images/icons/ct_logo.svg" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</caption>
|
</caption>
|
||||||
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="player__vac"></th>
|
<th class="player__vac"></th>
|
||||||
<th class="player__avatar"></th>
|
<th class="player__avatar"></th>
|
||||||
<th class="player__name"></th>
|
<th class="player__name"></th>
|
||||||
<th class="player__rank"></th>
|
<th class="player__rank"></th>
|
||||||
<th class="player__kills">K</th>
|
<th class="player__kills">K</th>
|
||||||
<th class="player__assist">A</th>
|
<th class="player__assist">A</th>
|
||||||
<th class="player__deaths">D</th>
|
<th class="player__deaths">D</th>
|
||||||
<th class="player__diff helptext" title="Kill death difference">+/-</th>
|
<th class="player__diff helptext" title="Kill death difference">
|
||||||
<th class="player__kd">K/D</th>
|
+/-
|
||||||
<th v-if="store.state.matchDetails.parsed" class="player__adr helptext" title="Average damage per round">
|
</th>
|
||||||
ADR
|
<th class="player__kd">K/D</th>
|
||||||
</th>
|
<th
|
||||||
<th class="player__hs helptext" title="Percentage of kills with a headshot">HS%</th>
|
v-if="store.state.matchDetails.parsed"
|
||||||
<th class="player__rating helptext" title="Estimated HLTV Rating 1.0">Rating</th>
|
class="player__adr helptext"
|
||||||
<th class="player__mvp helptext" title="Most valuable player">MVP</th>
|
title="Average damage per round"
|
||||||
<th class="player__score">Score</th>
|
>
|
||||||
</tr>
|
ADR
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="player__hs helptext"
|
||||||
|
title="Percentage of kills with a headshot"
|
||||||
|
>
|
||||||
|
HS%
|
||||||
|
</th>
|
||||||
|
<th class="player__rating helptext" title="Estimated HLTV Rating 1.0">
|
||||||
|
Rating
|
||||||
|
</th>
|
||||||
|
<th class="player__mvp helptext" title="Most valuable player">MVP</th>
|
||||||
|
<th class="player__score">Score</th>
|
||||||
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="player in teamStats(1)"
|
<tr
|
||||||
|
v-for="player in teamStats(1)"
|
||||||
:key="player.player.steamid64"
|
:key="player.player.steamid64"
|
||||||
class="team-1">
|
class="team-1"
|
||||||
<ScoreTeamPlayer :assists="player.assists"
|
>
|
||||||
:avatar="player.player.avatar"
|
<ScoreTeamPlayer
|
||||||
:color="player.color"
|
:assists="player.assists"
|
||||||
:deaths="player.deaths"
|
:avatar="player.player.avatar"
|
||||||
:dmg="player.dmg?.enemy"
|
:color="player.color"
|
||||||
:game_ban="player.player.game_ban"
|
:deaths="player.deaths"
|
||||||
:game_ban_date="player.player.game_ban_date"
|
:dmg="player.dmg?.enemy"
|
||||||
:hs="player.headshot"
|
:game_ban="player.player.game_ban"
|
||||||
:kdiff="player.kills - player.deaths"
|
:game_ban_date="player.player.game_ban_date"
|
||||||
:kills="player.kills"
|
:hs="player.headshot"
|
||||||
:mk_duo="player.multi_kills?.duo"
|
:kdiff="player.kills - player.deaths"
|
||||||
:mk_pent="player.multi_kills?.pent"
|
:kills="player.kills"
|
||||||
:mk_quad="player.multi_kills?.quad"
|
:mk_duo="player.multi_kills?.duo"
|
||||||
:mk_triple="player.multi_kills?.triple"
|
:mk_pent="player.multi_kills?.pent"
|
||||||
:mvp="player.mvp"
|
:mk_quad="player.multi_kills?.quad"
|
||||||
:name="player.player.name"
|
:mk_triple="player.multi_kills?.triple"
|
||||||
:parsed="store.state.matchDetails.parsed"
|
:mvp="player.mvp"
|
||||||
:player_score="player.score"
|
:name="player.player.name"
|
||||||
:rank_new="player.rank?.new"
|
:parsed="store.state.matchDetails.parsed"
|
||||||
:rank_old="player.rank?.old"
|
:player_score="player.score"
|
||||||
:rounds_played="store.state.matchDetails.score.reduce((a, b) => a + b)"
|
:rank_new="player.rank?.new"
|
||||||
:steamid64="player.player.steamid64"
|
:rank_old="player.rank?.old"
|
||||||
:tracked="player.player.tracked"
|
:rounds_played="
|
||||||
:vac="player.player.vac"
|
store.state.matchDetails.score.reduce((a, b) => a + b)
|
||||||
:vac_date="player.player.vac_date"
|
"
|
||||||
/>
|
:steamid64="player.player.steamid64"
|
||||||
</tr>
|
:tracked="player.player.tracked"
|
||||||
|
:vac="player.player.vac"
|
||||||
|
:vac_date="player.player.vac_date"
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<tr class="hr_outer">
|
<tr class="hr_outer">
|
||||||
<td colspan="14"></td>
|
<td colspan="14"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="hr">
|
<tr class="hr">
|
||||||
<td colspan="14"></td>
|
<td colspan="14"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="hr_outer">
|
<tr class="hr_outer">
|
||||||
<td colspan="14"></td>
|
<td colspan="14"></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr v-for="player in teamStats(2)"
|
<tr
|
||||||
|
v-for="player in teamStats(2)"
|
||||||
:key="player.player.steamid64"
|
:key="player.player.steamid64"
|
||||||
class="team-2">
|
class="team-2"
|
||||||
<ScoreTeamPlayer :assists="player.assists"
|
>
|
||||||
:avatar="player.player.avatar"
|
<ScoreTeamPlayer
|
||||||
:color="player.color"
|
:assists="player.assists"
|
||||||
:deaths="player.deaths"
|
:avatar="player.player.avatar"
|
||||||
:dmg="player.dmg?.enemy"
|
:color="player.color"
|
||||||
:game_ban="player.player.game_ban"
|
:deaths="player.deaths"
|
||||||
:game_ban_date="player.player.game_ban_date"
|
:dmg="player.dmg?.enemy"
|
||||||
:hs="player.headshot"
|
:game_ban="player.player.game_ban"
|
||||||
:kdiff="player.kills - player.deaths"
|
:game_ban_date="player.player.game_ban_date"
|
||||||
:kills="player.kills"
|
:hs="player.headshot"
|
||||||
:mk_duo="player.multi_kills?.duo"
|
:kdiff="player.kills - player.deaths"
|
||||||
:mk_pent="player.multi_kills?.pent"
|
:kills="player.kills"
|
||||||
:mk_quad="player.multi_kills?.quad"
|
:mk_duo="player.multi_kills?.duo"
|
||||||
:mk_triple="player.multi_kills?.triple"
|
:mk_pent="player.multi_kills?.pent"
|
||||||
:mvp="player.mvp"
|
:mk_quad="player.multi_kills?.quad"
|
||||||
:name="player.player.name"
|
:mk_triple="player.multi_kills?.triple"
|
||||||
:parsed="store.state.matchDetails.parsed"
|
:mvp="player.mvp"
|
||||||
:player_score="player.score"
|
:name="player.player.name"
|
||||||
:rank_new="player.rank?.new"
|
:parsed="store.state.matchDetails.parsed"
|
||||||
:rank_old="player.rank?.old"
|
:player_score="player.score"
|
||||||
:rounds_played="store.state.matchDetails.score.reduce((a, b) => a + b)"
|
:rank_new="player.rank?.new"
|
||||||
:steamid64="player.player.steamid64"
|
:rank_old="player.rank?.old"
|
||||||
:tracked="player.player.tracked"
|
:rounds_played="
|
||||||
:vac="player.player.vac"
|
store.state.matchDetails.score.reduce((a, b) => a + b)
|
||||||
:vac_date="player.player.vac_date"
|
"
|
||||||
/>
|
:steamid64="player.player.steamid64"
|
||||||
</tr>
|
:tracked="player.player.tracked"
|
||||||
|
:vac="player.player.vac"
|
||||||
|
:vac_date="player.player.vac_date"
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ScoreTeamPlayer from '@/components/ScoreTeamPlayer.vue'
|
import ScoreTeamPlayer from "/src/components/ScoreTeamPlayer.vue";
|
||||||
import {useStore} from "vuex";
|
import { useStore } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ScoreTeam',
|
name: "ScoreTeam",
|
||||||
components: {ScoreTeamPlayer},
|
components: { ScoreTeamPlayer },
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore()
|
const store = useStore();
|
||||||
|
|
||||||
const teamStats = (team) => {
|
const teamStats = (team) => {
|
||||||
let arr = []
|
let arr = [];
|
||||||
|
|
||||||
if (team === 1) {
|
if (team === 1) {
|
||||||
arr = []
|
arr = [];
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
arr.push(store.state.matchDetails.stats[i])
|
arr.push(store.state.matchDetails.stats[i]);
|
||||||
}
|
}
|
||||||
} else if (team === 2) {
|
} else if (team === 2) {
|
||||||
arr = []
|
arr = [];
|
||||||
for (let i = 5; i < store.state.matchDetails.stats.length; i++) {
|
for (let i = 5; i < store.state.matchDetails.stats.length; i++) {
|
||||||
arr.push(store.state.matchDetails.stats[i])
|
arr.push(store.state.matchDetails.stats[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return arr
|
return arr;
|
||||||
}
|
};
|
||||||
|
|
||||||
return {store, teamStats}
|
return { store, teamStats };
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -222,7 +303,7 @@ table {
|
|||||||
.team-2 {
|
.team-2 {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
opacity: .8;
|
opacity: 0.8;
|
||||||
|
|
||||||
margin-left: -100px;
|
margin-left: -100px;
|
||||||
|
|
||||||
@@ -267,7 +348,8 @@ table {
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.team-1, tr.team-2 {
|
tr.team-1,
|
||||||
|
tr.team-2 {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,7 +372,6 @@ table {
|
|||||||
.player__vac {
|
.player__vac {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
|
|||||||
@@ -1,29 +1,68 @@
|
|||||||
<template>
|
<template>
|
||||||
<td class="player__vac">
|
<td class="player__vac">
|
||||||
<div v-if="!props.vac && !props.game_ban" class="vac-placeholder"></div>
|
<div v-if="!props.vac && !props.game_ban" class="vac-placeholder"></div>
|
||||||
<img v-if="props.vac && FormatVacDate(props.vac_date, store.state.matchDetails.date) !== ''"
|
<img
|
||||||
:title="'Vac-banned: ' + FormatVacDate(props.vac_date, store.state.matchDetails.date)"
|
v-if="
|
||||||
alt="VAC-Ban"
|
props.vac &&
|
||||||
src="/images/icons/vac_banned.svg">
|
FormatVacDate(props.vac_date, store.state.matchDetails.date) !== ''
|
||||||
<img v-if="!props.vac && props.game_ban && FormatVacDate(props.game_ban_date, store.state.matchDetails.date) !== ''"
|
"
|
||||||
:title="'Game-banned: ' + FormatVacDate(props.game_ban_date, store.state.matchDetails.date)"
|
:title="
|
||||||
alt="Game-Ban"
|
'Vac-banned: ' +
|
||||||
src="/images/icons/game_banned.svg">
|
FormatVacDate(props.vac_date, store.state.matchDetails.date)
|
||||||
|
"
|
||||||
|
alt="VAC-Ban"
|
||||||
|
src="/images/icons/vac_banned.svg"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
v-if="
|
||||||
|
!props.vac &&
|
||||||
|
props.game_ban &&
|
||||||
|
FormatVacDate(props.game_ban_date, store.state.matchDetails.date) !== ''
|
||||||
|
"
|
||||||
|
:title="
|
||||||
|
'Game-banned: ' +
|
||||||
|
FormatVacDate(props.game_ban_date, store.state.matchDetails.date)
|
||||||
|
"
|
||||||
|
alt="Game-Ban"
|
||||||
|
src="/images/icons/game_banned.svg"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<img :class="'team-color-' + props.color" :src="constructAvatarUrl(props.avatar)" alt="Player avatar"
|
<img
|
||||||
class="player__avatar">
|
:class="'team-color-' + props.color"
|
||||||
|
:src="constructAvatarUrl(props.avatar)"
|
||||||
|
alt="Player avatar"
|
||||||
|
class="player__avatar"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td class="player__name" @click="GoToPlayer(props.steamid64)">
|
<td class="player__name" @click="GoToPlayer(props.steamid64)">
|
||||||
<i v-if="props.tracked" class="fa fa-dot-circle-o text-success tracked" title="Tracked user"></i>
|
<i
|
||||||
|
v-if="props.tracked"
|
||||||
|
class="fa fa-dot-circle-o text-success tracked"
|
||||||
|
title="Tracked user"
|
||||||
|
></i>
|
||||||
{{ props.name }}
|
{{ props.name }}
|
||||||
<i class="fa fa-external-link"></i>
|
<i class="fa fa-external-link"></i>
|
||||||
</td>
|
</td>
|
||||||
<td v-if="props.parsed" class="player__rank">
|
<td v-if="props.parsed" class="player__rank">
|
||||||
<img :alt="DisplayRank(props.rank_old)[1]"
|
<img
|
||||||
:class="props.rank_new > props.rank_old ? 'uprank' : props.rank_new < props.rank_old ? 'downrank' : ''"
|
:alt="DisplayRank(props.rank_old)[1]"
|
||||||
:src="DisplayRank(props.rank_old)[0]"
|
:class="
|
||||||
:title="props.rank_new > props.rank_old ? 'Uprank to ' + DisplayRank(props.rank_new)[1] : props.rank_new < props.rank_old ? 'Downrank to ' + DisplayRank(props.rank_new)[1] : DisplayRank(props.rank_old)[1]">
|
props.rank_new > props.rank_old
|
||||||
|
? 'uprank'
|
||||||
|
: props.rank_new < props.rank_old
|
||||||
|
? 'downrank'
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
:src="DisplayRank(props.rank_old)[0]"
|
||||||
|
:title="
|
||||||
|
props.rank_new > props.rank_old
|
||||||
|
? 'Uprank to ' + DisplayRank(props.rank_new)[1]
|
||||||
|
: props.rank_new < props.rank_old
|
||||||
|
? 'Downrank to ' + DisplayRank(props.rank_new)[1]
|
||||||
|
: DisplayRank(props.rank_old)[1]
|
||||||
|
"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td v-if="!props.parsed" class="rank-placeholder"></td>
|
<td v-if="!props.parsed" class="rank-placeholder"></td>
|
||||||
<td class="player__kills">
|
<td class="player__kills">
|
||||||
@@ -35,23 +74,42 @@
|
|||||||
<td class="player__deaths">
|
<td class="player__deaths">
|
||||||
{{ props.deaths }}
|
{{ props.deaths }}
|
||||||
</td>
|
</td>
|
||||||
<td :class="props.kdiff >= 0 ? 'text-success' : 'text-danger'" class="player__kdiff">
|
<td
|
||||||
|
:class="props.kdiff >= 0 ? 'text-success' : 'text-danger'"
|
||||||
|
class="player__kdiff"
|
||||||
|
>
|
||||||
{{ props.kdiff }}
|
{{ props.kdiff }}
|
||||||
</td>
|
</td>
|
||||||
<td class="player__kd">
|
<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
|
props.kills > 0 && props.deaths > 0
|
||||||
|
? (props.kills / props.deaths).toFixed(2)
|
||||||
|
: props.kills > 0 && props.deaths === 0
|
||||||
|
? props.kills
|
||||||
|
: 0.0
|
||||||
}}
|
}}
|
||||||
</td>
|
</td>
|
||||||
<td v-if="props.parsed" class="player__adr">
|
<td v-if="props.parsed" class="player__adr">
|
||||||
{{ (props.dmg / props.rounds_played).toFixed(2) }}
|
{{ (props.dmg / props.rounds_played).toFixed(2) }}
|
||||||
</td>
|
</td>
|
||||||
<td class="player__hs">
|
<td class="player__hs">
|
||||||
{{ (props.hs > 0 && props.kills > 0) ? (props.hs * 100 / props.kills).toFixed(0) + "%" : "0%" }}
|
{{
|
||||||
|
props.hs > 0 && props.kills > 0
|
||||||
|
? ((props.hs * 100) / props.kills).toFixed(0) + "%"
|
||||||
|
: "0%"
|
||||||
|
}}
|
||||||
</td>
|
</td>
|
||||||
<td class="player__rating">
|
<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)
|
GetHLTV_1(
|
||||||
|
props.kills,
|
||||||
|
props.rounds_played,
|
||||||
|
props.deaths,
|
||||||
|
props.mk_duo,
|
||||||
|
props.mk_triple,
|
||||||
|
props.mk_quad,
|
||||||
|
props.mk_pent
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
</td>
|
</td>
|
||||||
<td class="player__mvp">
|
<td class="player__mvp">
|
||||||
@@ -63,143 +121,157 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {constructAvatarUrl, DisplayRank, FormatVacDate, GetHLTV_1, GoToPlayer} from "@/utils";
|
import {
|
||||||
import {useStore} from "vuex";
|
constructAvatarUrl,
|
||||||
|
DisplayRank,
|
||||||
|
FormatVacDate,
|
||||||
|
GetHLTV_1,
|
||||||
|
GoToPlayer,
|
||||||
|
} from "/src/utils";
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ScoreTeamPlayer',
|
name: "ScoreTeamPlayer",
|
||||||
props: {
|
props: {
|
||||||
steamid64: {
|
steamid64: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
default: ''
|
default: "",
|
||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
default: 'Avatar'
|
default: "Avatar",
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
default: 'Name'
|
default: "Name",
|
||||||
},
|
},
|
||||||
rank_old: {
|
rank_old: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
rank_new: {
|
rank_new: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
kills: {
|
kills: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
assists: {
|
assists: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
deaths: {
|
deaths: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
kdiff: {
|
kdiff: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
hs: {
|
hs: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
rounds_played: {
|
rounds_played: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
mk_duo: {
|
mk_duo: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
mk_triple: {
|
mk_triple: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
mk_quad: {
|
mk_quad: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
mk_pent: {
|
mk_pent: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
dmg: {
|
dmg: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
mvp: {
|
mvp: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
player_score: {
|
player_score: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
default: ''
|
default: "",
|
||||||
},
|
},
|
||||||
tracked: {
|
tracked: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
default: false
|
default: false,
|
||||||
},
|
},
|
||||||
parsed: {
|
parsed: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
default: false
|
default: false,
|
||||||
},
|
},
|
||||||
vac: {
|
vac: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
default: false
|
default: false,
|
||||||
},
|
},
|
||||||
vac_date: {
|
vac_date: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: false,
|
required: false,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
game_ban: {
|
game_ban: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
default: false
|
default: false,
|
||||||
},
|
},
|
||||||
game_ban_date: {
|
game_ban_date: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: false,
|
required: false,
|
||||||
default: 0
|
default: 0,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const store = useStore()
|
const store = useStore();
|
||||||
return {props, GetHLTV_1, GoToPlayer, DisplayRank, constructAvatarUrl, FormatVacDate, store}
|
return {
|
||||||
}
|
props,
|
||||||
}
|
GetHLTV_1,
|
||||||
|
GoToPlayer,
|
||||||
|
DisplayRank,
|
||||||
|
constructAvatarUrl,
|
||||||
|
FormatVacDate,
|
||||||
|
store,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -231,11 +303,11 @@ export default {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.tracked {
|
.tracked {
|
||||||
font-size: .8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fa-external-link {
|
.fa-external-link {
|
||||||
font-size: .8rem;
|
font-size: 0.8rem;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,11 +322,18 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.player__kills, .player__assist, .player__deaths, .player__kdiff, .player__mvp {
|
.player__kills,
|
||||||
|
.player__assist,
|
||||||
|
.player__deaths,
|
||||||
|
.player__kdiff,
|
||||||
|
.player__mvp {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player__kd, .player__hs, .player__rating, .player__score {
|
.player__kd,
|
||||||
|
.player__hs,
|
||||||
|
.player__rating,
|
||||||
|
.player__score {
|
||||||
width: 75px;
|
width: 75px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,24 +3,25 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {watch} from "vue";
|
import { watch } from "vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SprayGraph",
|
name: "SprayGraph",
|
||||||
props: {
|
props: {
|
||||||
spray: {
|
spray: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
watch(() => props.spray, () => {
|
watch(
|
||||||
// console.log(props.spray)
|
() => props.spray,
|
||||||
})
|
() => {
|
||||||
}
|
// console.log(props.spray)
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style lang="scss" scoped></style>
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -6,138 +6,158 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as echarts from 'echarts/core';
|
import * as echarts from "echarts/core";
|
||||||
import {GridComponent, LegendComponent, TooltipComponent} from 'echarts/components';
|
import {
|
||||||
import {BarChart} from 'echarts/charts';
|
GridComponent,
|
||||||
import {CanvasRenderer} from 'echarts/renderers';
|
LegendComponent,
|
||||||
import {onMounted, onUnmounted, ref} from "vue";
|
TooltipComponent,
|
||||||
import {checkStatEmpty, getPlayerArr} from "../utils";
|
} from "echarts/components";
|
||||||
import {useStore} from "vuex";
|
import { BarChart } from "echarts/charts";
|
||||||
|
import { CanvasRenderer } from "echarts/renderers";
|
||||||
|
import { onMounted, onUnmounted, ref } from "vue";
|
||||||
|
import { checkStatEmpty, getPlayerArr } from "../utils";
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "FlashChart",
|
name: "FlashChart",
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore()
|
const store = useStore();
|
||||||
|
|
||||||
let myChart1, myChart2
|
let myChart1, myChart2;
|
||||||
|
|
||||||
const getWindowWidth = () => {
|
const getWindowWidth = () => {
|
||||||
const windowWidth = window.innerWidth
|
const windowWidth = window.innerWidth;
|
||||||
if (windowWidth <= 750)
|
if (windowWidth <= 750) return windowWidth;
|
||||||
return windowWidth
|
else return 650;
|
||||||
else
|
};
|
||||||
return 650
|
|
||||||
}
|
|
||||||
|
|
||||||
const setHeight = () => {
|
const setHeight = () => {
|
||||||
const windowWidth = getWindowWidth()
|
const windowWidth = getWindowWidth();
|
||||||
if (windowWidth >= 751)
|
if (windowWidth >= 751) return (windowWidth * 3) / 7.5;
|
||||||
return windowWidth * 3 / 7.5
|
|
||||||
else if (windowWidth >= 501 && windowWidth <= 750)
|
else if (windowWidth >= 501 && windowWidth <= 750)
|
||||||
return windowWidth * 3 / 6.5
|
return (windowWidth * 3) / 6.5;
|
||||||
else
|
else return (windowWidth * 3) / 5.5;
|
||||||
return windowWidth * 3 / 5.5
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const width = ref(getWindowWidth())
|
const width = ref(getWindowWidth());
|
||||||
const height = ref(setHeight())
|
const height = ref(setHeight());
|
||||||
|
|
||||||
const dataArr = (stats, team, prop) => {
|
const dataArr = (stats, team, prop) => {
|
||||||
if (['team', 'enemy', 'self'].indexOf(prop) > -1) {
|
if (["team", "enemy", "self"].indexOf(prop) > -1) {
|
||||||
let arr = []
|
let arr = [];
|
||||||
for (let i = (team - 1) * 5; i < team * 5; i++) {
|
for (let i = (team - 1) * 5; i < team * 5; i++) {
|
||||||
arr.push({
|
arr.push({
|
||||||
value: checkStatEmpty(Function('return(function(stats, i){ return stats[i].dmg.' + prop + '})')()(stats, i)) * (prop === 'enemy' ? 1 : -1),
|
value:
|
||||||
|
checkStatEmpty(
|
||||||
|
Function(
|
||||||
|
"return(function(stats, i){ return stats[i].dmg." +
|
||||||
|
prop +
|
||||||
|
"})"
|
||||||
|
)()(stats, i)
|
||||||
|
) * (prop === "enemy" ? 1 : -1),
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: prop === 'enemy' ? getComputedStyle(document.documentElement).getPropertyValue(`--csgo-${stats[i].color}`) : 'firebrick'
|
color:
|
||||||
}
|
prop === "enemy"
|
||||||
})
|
? getComputedStyle(document.documentElement).getPropertyValue(
|
||||||
|
`--csgo-${stats[i].color}`
|
||||||
|
)
|
||||||
|
: "firebrick",
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
arr.reverse()
|
arr.reverse();
|
||||||
return arr
|
return arr;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const optionGen = (team) => {
|
const optionGen = (team) => {
|
||||||
return {
|
return {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: "axis",
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
type: 'shadow'
|
type: "shadow",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
show: false
|
show: false,
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
left: '3%',
|
left: "3%",
|
||||||
right: '4%',
|
right: "4%",
|
||||||
bottom: '3%',
|
bottom: "3%",
|
||||||
containLabel: true
|
containLabel: true,
|
||||||
},
|
},
|
||||||
xAxis: [
|
xAxis: [
|
||||||
{
|
{
|
||||||
type: 'value',
|
type: "value",
|
||||||
min: -300
|
min: -300,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
yAxis: [
|
yAxis: [
|
||||||
{
|
{
|
||||||
type: 'category',
|
type: "category",
|
||||||
axisTick: {
|
axisTick: {
|
||||||
show: false
|
show: false,
|
||||||
},
|
},
|
||||||
data: getPlayerArr(store.state.matchDetails.stats, team)
|
data: getPlayerArr(store.state.matchDetails.stats, team),
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: 'Team',
|
name: "Team",
|
||||||
type: 'bar',
|
type: "bar",
|
||||||
stack: 'Total',
|
stack: "Total",
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
emphasis: {
|
emphasis: {
|
||||||
focus: 'series'
|
focus: "series",
|
||||||
},
|
},
|
||||||
data: dataArr(store.state.matchDetails.stats, team, 'team')
|
data: dataArr(store.state.matchDetails.stats, team, "team"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Enemy',
|
name: "Enemy",
|
||||||
type: 'bar',
|
type: "bar",
|
||||||
stack: 'Total',
|
stack: "Total",
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
position: 'inside'
|
position: "inside",
|
||||||
},
|
},
|
||||||
emphasis: {
|
emphasis: {
|
||||||
focus: 'series'
|
focus: "series",
|
||||||
},
|
},
|
||||||
data: dataArr(store.state.matchDetails.stats, team, 'enemy')
|
data: dataArr(store.state.matchDetails.stats, team, "enemy"),
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const disposeCharts = () => {
|
const disposeCharts = () => {
|
||||||
if (myChart1 != null && myChart1 !== '' && myChart1 !== undefined) {
|
if (myChart1 != null && myChart1 !== "" && myChart1 !== undefined) {
|
||||||
myChart1.dispose()
|
myChart1.dispose();
|
||||||
}
|
}
|
||||||
if (myChart2 != null && myChart2 !== '' && myChart2 !== undefined) {
|
if (myChart2 != null && myChart2 !== "" && myChart2 !== undefined) {
|
||||||
myChart2.dispose()
|
myChart2.dispose();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const buildCharts = () => {
|
const buildCharts = () => {
|
||||||
disposeCharts()
|
disposeCharts();
|
||||||
|
|
||||||
myChart1 = echarts.init(document.getElementById('dmg-chart-1'), {}, {width: width.value, height: height.value});
|
myChart1 = echarts.init(
|
||||||
|
document.getElementById("dmg-chart-1"),
|
||||||
|
{},
|
||||||
|
{ width: width.value, height: height.value }
|
||||||
|
);
|
||||||
myChart1.setOption(optionGen(1));
|
myChart1.setOption(optionGen(1));
|
||||||
|
|
||||||
myChart2 = echarts.init(document.getElementById('dmg-chart-2'), {}, {width: width.value, height: height.value});
|
myChart2 = echarts.init(
|
||||||
|
document.getElementById("dmg-chart-2"),
|
||||||
|
{},
|
||||||
|
{ width: width.value, height: height.value }
|
||||||
|
);
|
||||||
myChart2.setOption(optionGen(2));
|
myChart2.setOption(optionGen(2));
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (store.state.matchDetails.stats) {
|
if (store.state.matchDetails.stats) {
|
||||||
@@ -146,27 +166,27 @@ export default {
|
|||||||
GridComponent,
|
GridComponent,
|
||||||
LegendComponent,
|
LegendComponent,
|
||||||
BarChart,
|
BarChart,
|
||||||
CanvasRenderer
|
CanvasRenderer,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
buildCharts()
|
buildCharts();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
disposeCharts()
|
disposeCharts();
|
||||||
})
|
});
|
||||||
|
|
||||||
window.onresize = () => {
|
window.onresize = () => {
|
||||||
if (window.innerWidth <= 750) {
|
if (window.innerWidth <= 750) {
|
||||||
width.value = getWindowWidth() - 20
|
width.value = getWindowWidth() - 20;
|
||||||
height.value = setHeight()
|
height.value = setHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
buildCharts()
|
buildCharts();
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -186,6 +206,5 @@ export default {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="toggle-btn text-muted">
|
<div class="toggle-btn text-muted">
|
||||||
<div @click.prevent="$emit('translated', handleBtnClick())"
|
<div class="d-flex" @click.prevent="handleBtnClick">
|
||||||
class="d-flex">
|
|
||||||
<span class="text-center mx-2">
|
<span class="text-center mx-2">
|
||||||
<i id="toggle-off" class="fa fa-toggle-off show"/>
|
<i id="toggle-off" class="fa fa-toggle-off show" />
|
||||||
<i id="toggle-on" class="fa fa-toggle-on"/>
|
<i id="toggle-on" class="fa fa-toggle-on" />
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<span :class="toggle === 'translated' ? 'text-warning' : ''"
|
<span
|
||||||
class="float-start">
|
:class="toggle === 'translated' ? 'text-warning' : ''"
|
||||||
<span class="text-uppercase">Translate to {{data.browserLang}}</span>
|
class="float-start"
|
||||||
|
>
|
||||||
|
<span class="text-uppercase"
|
||||||
|
>Translate to {{ data.browserLang }}</span
|
||||||
|
>
|
||||||
<span class="loading-icon ms-2" title="Translating..">
|
<span class="loading-icon ms-2" title="Translating..">
|
||||||
<i class="fa fa-spinner fa-pulse fa-fw"/>
|
<i class="fa fa-spinner fa-pulse fa-fw" />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -19,83 +22,84 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts" setup>
|
||||||
import {onMounted, reactive, ref} from "vue";
|
import { onMounted, reactive, ref } from "vue";
|
||||||
import ISO6391 from 'iso-639-1'
|
import ISO6391 from "iso-639-1";
|
||||||
import {GetChatHistoryTranslated} from "@/utils";
|
import { GetChatHistoryTranslated } from "@/utils";
|
||||||
import {useStore} from "vuex";
|
import { useMatchDetailsStore } from "@/stores/matchDetails";
|
||||||
|
import { useInfoStateStore } from "@/stores/infoState";
|
||||||
|
|
||||||
export default {
|
const matchDetailsStore = useMatchDetailsStore();
|
||||||
name: 'TranslateChatButton',
|
const infoStateStore = useInfoStateStore();
|
||||||
props: {
|
|
||||||
translated: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const data = reactive({
|
const emit = defineEmits<{
|
||||||
browserIsoCode: '',
|
(e: "translated", toggle: string): void;
|
||||||
browserLangCode: '',
|
}>();
|
||||||
browserLang: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
const toggle = ref('original')
|
const data = reactive({
|
||||||
|
browserIsoCode: "",
|
||||||
|
browserLangCode: "",
|
||||||
|
browserLang: "",
|
||||||
|
});
|
||||||
|
|
||||||
const setLanguageVariables = () => {
|
const toggle = ref("original");
|
||||||
const navLangs = navigator.languages
|
|
||||||
|
|
||||||
data.browserIsoCode = navLangs.find((l) => l.length === 5)
|
const setLanguageVariables = () => {
|
||||||
data.browserLangCode = navLangs[0]
|
const navLangs = navigator.languages;
|
||||||
|
|
||||||
if (ISO6391.validate(data.browserLangCode)) {
|
data.browserIsoCode = navLangs.find((l) => l.length === 5) || "";
|
||||||
data.browserLang = ISO6391.getNativeName(data.browserLangCode)
|
data.browserLangCode = navLangs[0];
|
||||||
} else {
|
|
||||||
data.browserIsoCode = 'en-US'
|
|
||||||
data.browserLangCode = 'en'
|
|
||||||
data.browserLang = 'English'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBtnClick = async () => {
|
if (ISO6391.validate(data.browserLangCode)) {
|
||||||
let response
|
data.browserLang = ISO6391.getNativeName(data.browserLangCode);
|
||||||
|
} else {
|
||||||
|
data.browserIsoCode = "en-US";
|
||||||
|
data.browserLangCode = "en";
|
||||||
|
data.browserLang = "English";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const refreshButton = document.querySelector('.loading-icon .fa-spinner')
|
const handleBtnClick = async () => {
|
||||||
refreshButton.classList.add('show')
|
const refreshButton = document.querySelector(
|
||||||
|
".loading-icon .fa-spinner"
|
||||||
|
) as HTMLElement;
|
||||||
|
refreshButton.classList.add("show");
|
||||||
|
|
||||||
toggleShow()
|
// TODO: Add langCode
|
||||||
|
const [response, info] = await GetChatHistoryTranslated(
|
||||||
|
matchDetailsStore.matchDetails.match_id
|
||||||
|
);
|
||||||
|
|
||||||
response = await GetChatHistoryTranslated(store, store.state.matchDetails.match_id)
|
if (info.message !== "") infoStateStore.addInfo(info);
|
||||||
|
if (response !== null) {
|
||||||
|
matchDetailsStore.matchChat = response;
|
||||||
|
|
||||||
if (refreshButton.classList.contains('show'))
|
toggleShow();
|
||||||
refreshButton.classList.remove('show')
|
if (refreshButton.classList.contains("show"))
|
||||||
|
refreshButton.classList.remove("show");
|
||||||
|
}
|
||||||
|
|
||||||
return [response, toggle.value]
|
emit("translated", toggle.value);
|
||||||
}
|
};
|
||||||
|
|
||||||
const toggleShow = () => {
|
const toggleShow = () => {
|
||||||
const offBtn = document.getElementById('toggle-off')
|
const offBtn = document.getElementById("toggle-off") as HTMLElement;
|
||||||
const onBtn = document.getElementById('toggle-on')
|
const onBtn = document.getElementById("toggle-on") as HTMLElement;
|
||||||
|
|
||||||
if (offBtn.classList.contains('show')) {
|
if (offBtn.classList.contains("show")) {
|
||||||
offBtn.classList.remove('show')
|
offBtn.classList.remove("show");
|
||||||
onBtn.classList.add('show')
|
onBtn.classList.add("show");
|
||||||
toggle.value = 'translated'
|
toggle.value = "translated";
|
||||||
} else if (onBtn.classList.contains('show')) {
|
} else if (onBtn.classList.contains("show")) {
|
||||||
onBtn.classList.remove('show')
|
onBtn.classList.remove("show");
|
||||||
offBtn.classList.add('show')
|
offBtn.classList.add("show");
|
||||||
toggle.value = 'original'
|
toggle.value = "original";
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setLanguageVariables()
|
setLanguageVariables();
|
||||||
})
|
});
|
||||||
return {data, toggle, handleBtnClick}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :style="props.ud.flames || props.ud.flash || props.ud.he ? 'display: flex' : 'display: none'"
|
<div
|
||||||
class="player-utility">
|
:style="
|
||||||
|
props.ud.flames || props.ud.flash || props.ud.he
|
||||||
|
? 'display: flex'
|
||||||
|
: 'display: none'
|
||||||
|
"
|
||||||
|
class="player-utility"
|
||||||
|
>
|
||||||
<div class="heading">
|
<div class="heading">
|
||||||
<img :src="props.avatar" alt="Player avatar" class="avatar">
|
<img :src="props.avatar" alt="Player avatar" class="avatar" />
|
||||||
<h4>{{ props.name }}</h4>
|
<h4>{{ props.name }}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div :id="'utility-chart-' + props.id"></div>
|
<div :id="'utility-chart-' + props.id"></div>
|
||||||
@@ -10,13 +16,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as echarts from 'echarts/core';
|
import * as echarts from "echarts/core";
|
||||||
import {LegendComponent, TooltipComponent} from 'echarts/components';
|
import {
|
||||||
import {PieChart} from 'echarts/charts';
|
LegendComponent,
|
||||||
import {LabelLayout} from 'echarts/features';
|
TitleComponent,
|
||||||
import {CanvasRenderer} from 'echarts/renderers';
|
TooltipComponent,
|
||||||
import { TitleComponent } from 'echarts/components';
|
} from "echarts/components";
|
||||||
import {onMounted} from "vue";
|
import { PieChart } from "echarts/charts";
|
||||||
|
import { LabelLayout } from "echarts/features";
|
||||||
|
import { CanvasRenderer } from "echarts/renderers";
|
||||||
|
import { onMounted } from "vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "FlashChart",
|
name: "FlashChart",
|
||||||
@@ -24,21 +33,21 @@ export default {
|
|||||||
id: {
|
id: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: "",
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: "",
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
ud: {
|
ud: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
@@ -49,87 +58,100 @@ export default {
|
|||||||
PieChart,
|
PieChart,
|
||||||
CanvasRenderer,
|
CanvasRenderer,
|
||||||
TitleComponent,
|
TitleComponent,
|
||||||
LabelLayout
|
LabelLayout,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let myChart = echarts.init(document.getElementById(`utility-chart-${props.id}`), {}, {width: 500, height: 300});
|
let myChart = echarts.init(
|
||||||
let option
|
document.getElementById(`utility-chart-${props.id}`),
|
||||||
|
{},
|
||||||
|
{ width: 500, height: 300 }
|
||||||
|
);
|
||||||
|
let option;
|
||||||
|
|
||||||
option = {
|
option = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'item',
|
trigger: "item",
|
||||||
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
formatter: "{a} <br/>{b}: {c} ({d}%)",
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
show: false
|
show: false,
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: 'Utility Damage',
|
name: "Utility Damage",
|
||||||
type: 'pie',
|
type: "pie",
|
||||||
radius: [0, '65%'],
|
radius: [0, "65%"],
|
||||||
avoidLabelOverlap: true,
|
avoidLabelOverlap: true,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
borderColor: '#000',
|
borderColor: "#000",
|
||||||
borderWidth: 3
|
borderWidth: 3,
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
position: 'inside',
|
position: "inside",
|
||||||
fontsize: 36,
|
fontsize: 36,
|
||||||
fontWeight: 'bold'
|
fontWeight: "bold",
|
||||||
},
|
},
|
||||||
labelLine: {
|
labelLine: {
|
||||||
show: false
|
show: false,
|
||||||
},
|
},
|
||||||
data: [
|
data: [
|
||||||
(props.ud.flames ? {
|
props.ud.flames
|
||||||
value: props.ud.flames ? props.ud.flames : null,
|
? {
|
||||||
name: 'Flames',
|
value: props.ud.flames ? props.ud.flames : null,
|
||||||
itemStyle: {
|
name: "Flames",
|
||||||
color: '#FF4343FF'
|
itemStyle: {
|
||||||
}
|
color: "#FF4343FF",
|
||||||
} : {}),
|
},
|
||||||
(props.ud.he ? {
|
}
|
||||||
value: props.ud.he ? props.ud.he : null,
|
: {},
|
||||||
name: 'HE',
|
props.ud.he
|
||||||
itemStyle: {
|
? {
|
||||||
color: '#62c265'
|
value: props.ud.he ? props.ud.he : null,
|
||||||
}
|
name: "HE",
|
||||||
} : {})
|
itemStyle: {
|
||||||
,
|
color: "#62c265",
|
||||||
(props.ud.flash ? {
|
},
|
||||||
value: props.ud.flash ? props.ud.flash : null,
|
}
|
||||||
name: 'Flash',
|
: {},
|
||||||
itemStyle: {
|
props.ud.flash
|
||||||
color: '#18cff3'
|
? {
|
||||||
}
|
value: props.ud.flash ? props.ud.flash : null,
|
||||||
} : {}),
|
name: "Flash",
|
||||||
(props.ud.smoke ? {
|
itemStyle: {
|
||||||
value: props.ud.smoke ? props.ud.smoke : null,
|
color: "#18cff3",
|
||||||
name: 'Smoke',
|
},
|
||||||
itemStyle: {
|
}
|
||||||
color: '#6e6b78'
|
: {},
|
||||||
}
|
props.ud.smoke
|
||||||
} : {}),
|
? {
|
||||||
(props.ud.decoy ? {
|
value: props.ud.smoke ? props.ud.smoke : null,
|
||||||
value: props.ud.decoy ? props.ud.decoy : null,
|
name: "Smoke",
|
||||||
name: 'Decoy',
|
itemStyle: {
|
||||||
itemStyle: {
|
color: "#6e6b78",
|
||||||
color: '#e28428'
|
},
|
||||||
}
|
}
|
||||||
} : {})
|
: {},
|
||||||
]
|
props.ud.decoy
|
||||||
}
|
? {
|
||||||
]
|
value: props.ud.decoy ? props.ud.decoy : null,
|
||||||
|
name: "Decoy",
|
||||||
|
itemStyle: {
|
||||||
|
color: "#e28428",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
myChart.setOption(option);
|
myChart.setOption(option);
|
||||||
})
|
});
|
||||||
|
|
||||||
return {props}
|
return { props };
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -1,61 +1,67 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="utility-chart-total" v-if="props.stats">
|
<div v-if="props.stats" class="utility-chart-total">
|
||||||
<div class="heading">
|
<div class="heading">
|
||||||
<h4>Total Utility Damage</h4>
|
<h4>Total Utility Damage</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="utility-chart-total"></div>
|
<div id="utility-chart-total"></div>
|
||||||
<hr>
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as echarts from 'echarts/core';
|
import * as echarts from "echarts/core";
|
||||||
import {GridComponent, LegendComponent, TooltipComponent} from 'echarts/components';
|
import {
|
||||||
import {BarChart} from 'echarts/charts';
|
GridComponent,
|
||||||
import {CanvasRenderer} from 'echarts/renderers';
|
LegendComponent,
|
||||||
import {onMounted} from "vue";
|
TooltipComponent,
|
||||||
|
} from "echarts/components";
|
||||||
|
import { BarChart } from "echarts/charts";
|
||||||
|
import { CanvasRenderer } from "echarts/renderers";
|
||||||
|
import { onMounted } from "vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "FlashChart",
|
name: "FlashChart",
|
||||||
props: {
|
props: {
|
||||||
stats: {
|
stats: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const checkStatEmpty = (stat) => {
|
const checkStatEmpty = (stat) => {
|
||||||
if (stat)
|
if (stat) return stat;
|
||||||
return stat
|
else return 0;
|
||||||
else
|
};
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const seriesArr = (stats) => {
|
const seriesArr = (stats) => {
|
||||||
let arr = []
|
let arr = [];
|
||||||
for (let i = 0; i < stats.length; i++) {
|
for (let i = 0; i < stats.length; i++) {
|
||||||
const sum = checkStatEmpty(stats[i].dmg.ud.flames) + checkStatEmpty(stats[i].dmg.ud.flash) + checkStatEmpty(stats[i].dmg.ud.he) + checkStatEmpty(stats[i].dmg.ud.smoke)
|
const sum =
|
||||||
|
checkStatEmpty(stats[i].dmg.ud.flames) +
|
||||||
|
checkStatEmpty(stats[i].dmg.ud.flash) +
|
||||||
|
checkStatEmpty(stats[i].dmg.ud.he) +
|
||||||
|
checkStatEmpty(stats[i].dmg.ud.smoke);
|
||||||
|
|
||||||
if (sum !== 0) {
|
if (sum !== 0) {
|
||||||
arr.push({
|
arr.push({
|
||||||
name: stats[i].player.name,
|
name: stats[i].player.name,
|
||||||
type: 'bar',
|
type: "bar",
|
||||||
stack: 'total',
|
stack: "total",
|
||||||
label: {
|
label: {
|
||||||
show: true
|
show: true,
|
||||||
},
|
},
|
||||||
emphasis: {
|
emphasis: {
|
||||||
focus: 'series'
|
focus: "series",
|
||||||
},
|
},
|
||||||
data: [sum]
|
data: [sum],
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
arr.sort((a, b) => parseFloat(b.data[0]) - parseFloat(a.data[0]))
|
arr.sort((a, b) => parseFloat(b.data[0]) - parseFloat(a.data[0]));
|
||||||
|
|
||||||
return arr
|
return arr;
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
echarts.use([
|
echarts.use([
|
||||||
@@ -63,58 +69,62 @@ export default {
|
|||||||
GridComponent,
|
GridComponent,
|
||||||
LegendComponent,
|
LegendComponent,
|
||||||
BarChart,
|
BarChart,
|
||||||
CanvasRenderer
|
CanvasRenderer,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let myChart = echarts.init(document.getElementById('utility-chart-total'), {}, {width: 800, height: 200});
|
let myChart = echarts.init(
|
||||||
let option
|
document.getElementById("utility-chart-total"),
|
||||||
|
{},
|
||||||
|
{ width: 800, height: 200 }
|
||||||
|
);
|
||||||
|
let option;
|
||||||
|
|
||||||
option = {
|
option = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: "axis",
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
// Use axis to trigger tooltip
|
// Use axis to trigger tooltip
|
||||||
type: 'shadow' // 'shadow' as default; can also be 'line'
|
type: "shadow", // 'shadow' as default; can also be 'line'
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
// color: ['#143147', '#39546c', '#617a94', '#89a2bd', '#b3cce8', '#eac65c', '#bd9d2c', '#917501', '#685000', '#412c00'],
|
// color: ['#143147', '#39546c', '#617a94', '#89a2bd', '#b3cce8', '#eac65c', '#bd9d2c', '#917501', '#685000', '#412c00'],
|
||||||
// color: ['#003470', '#005a9b', '#0982c7', '#4bace5', '#90d3fe', '#febf4a', '#d7931c', '#ac6a01', '#804400', '#572000'],
|
// color: ['#003470', '#005a9b', '#0982c7', '#4bace5', '#90d3fe', '#febf4a', '#d7931c', '#ac6a01', '#804400', '#572000'],
|
||||||
// color: ['#888F98', '#10121A', '#1B2732', '#5F7892', '#C3A235'],
|
// color: ['#888F98', '#10121A', '#1B2732', '#5F7892', '#C3A235'],
|
||||||
legend: {
|
legend: {
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: 'white'
|
color: "white",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
left: '3%',
|
left: "3%",
|
||||||
right: '4%',
|
right: "4%",
|
||||||
bottom: '3%',
|
bottom: "3%",
|
||||||
containLabel: true
|
containLabel: true,
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'value'
|
type: "value",
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'category',
|
type: "category",
|
||||||
data: ['Total']
|
data: ["Total"],
|
||||||
},
|
},
|
||||||
aria: {
|
aria: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
show: true,
|
show: true,
|
||||||
decal: {
|
decal: {
|
||||||
show: true
|
show: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
series: seriesArr(props.stats)
|
series: seriesArr(props.stats),
|
||||||
};
|
};
|
||||||
|
|
||||||
myChart.setOption(option);
|
myChart.setOption(option);
|
||||||
})
|
});
|
||||||
|
|
||||||
return {props}
|
return { props };
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
export const SHARECODE_REGEX = /^CSGO(?:-?[ABCDEFGHJKLMNOPQRSTUVWXYZabcdefhijkmnopqrstuvwxyz23456789]{5}){5}$/
|
|
||||||
export const AUTHCODE_REGEX = /^[ABCDEFGHJKLMNOPQRSTUVWXYZ23456789]{4}-[ABCDEFGHJKLMNOPQRSTUVWXYZ23456789]{5}-[ABCDEFGHJKLMNOPQRSTUVWXYZ23456789]{4}$/
|
|
||||||
|
|
||||||
export const NAV_HEIGHT = 70
|
|
||||||
export const FOOTER_HEIGHT = 200
|
|
||||||
15
src/constants/index.ts
Normal file
15
src/constants/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export const SHARECODE_REGEX =
|
||||||
|
/^CSGO(?:-?[ABCDEFGHJKLMNOPQRSTUVWXYZabcdefhijkmnopqrstuvwxyz23456789]{5}){5}$/g;
|
||||||
|
export const AUTHCODE_REGEX =
|
||||||
|
/^[ABCDEFGHJKLMNOPQRSTUVWXYZ23456789]{4}-[ABCDEFGHJKLMNOPQRSTUVWXYZ23456789]{5}-[ABCDEFGHJKLMNOPQRSTUVWXYZ23456789]{4}$/g;
|
||||||
|
|
||||||
|
export const ID64_PATTERN = /^\d{17}$/g;
|
||||||
|
export const VANITY_PATTERN = /^[A-Za-z0-9-_]{3,32}$/g;
|
||||||
|
|
||||||
|
export const CUSTOM_URL_BASE = "https://steamcommunity.com/id/";
|
||||||
|
export const PROFILE_URL_BASE = "https://steamcommunity.com/profiles/";
|
||||||
|
export const MATCH_SHARE_URL_BASE =
|
||||||
|
"steam://rungame/730/76561202255233023/+csgo_download_match%20";
|
||||||
|
|
||||||
|
export const NAV_HEIGHT = 70;
|
||||||
|
export const FOOTER_HEIGHT = 200;
|
||||||
24
src/main.js
24
src/main.js
@@ -1,24 +0,0 @@
|
|||||||
import {createApp} from 'vue'
|
|
||||||
import App from './App.vue'
|
|
||||||
import router from './router'
|
|
||||||
import store from './store'
|
|
||||||
import 'bootstrap'
|
|
||||||
import '@/scss/custom.scss'
|
|
||||||
import VueMatomo from 'vue-matomo'
|
|
||||||
|
|
||||||
const app = createApp(App)
|
|
||||||
|
|
||||||
app.use(store)
|
|
||||||
app.use(router)
|
|
||||||
|
|
||||||
if (process.env.VUE_APP_TRACKING) {
|
|
||||||
app.use(
|
|
||||||
VueMatomo, {
|
|
||||||
host: process.env.VUE_APP_TRACK_URL,
|
|
||||||
siteId: process.env.VUE_APP_TRACK_ID,
|
|
||||||
router: router,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.mount('#app')
|
|
||||||
31
src/main.ts
Normal file
31
src/main.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { createApp } from "vue";
|
||||||
|
import { createPinia } from "pinia";
|
||||||
|
|
||||||
|
import App from "./App.vue";
|
||||||
|
import router from "./router";
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
import VueMatomo from "vue-matomo";
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
import store from "./store";
|
||||||
|
|
||||||
|
import "bootstrap";
|
||||||
|
import "bootstrap-icons/font/bootstrap-icons.css";
|
||||||
|
import "/src/scss/custom.scss";
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
|
||||||
|
app.use(createPinia());
|
||||||
|
app.use(router);
|
||||||
|
app.use(store);
|
||||||
|
|
||||||
|
if (import.meta.env.VITE_TRACKING) {
|
||||||
|
app.use(VueMatomo, {
|
||||||
|
host: import.meta.env.VITE_TRACK_URL,
|
||||||
|
siteId: import.meta.env.VITE_TRACK_ID,
|
||||||
|
router: router,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
app.mount("#app");
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
import {createRouter, createWebHistory} from 'vue-router'
|
|
||||||
|
|
||||||
function lazyLoadView(view) {
|
|
||||||
return () => import(`@/views/${view}.vue`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function lazyLoadComponent(view) {
|
|
||||||
return () => import(`@/components/${view}.vue`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function lazyLoadErrorPages(view) {
|
|
||||||
return () => import(`@/views/errorPages/${view}.vue`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const routes = [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
name: 'Home',
|
|
||||||
components: {
|
|
||||||
main: lazyLoadView('Home')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/privacy-policy',
|
|
||||||
name: 'PrivacyPolicy',
|
|
||||||
components: {
|
|
||||||
main: lazyLoadView('PrivacyPolicy')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/matches',
|
|
||||||
name: 'Explore',
|
|
||||||
components: {
|
|
||||||
main: lazyLoadView('Explore')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/player/:id',
|
|
||||||
name: 'Player',
|
|
||||||
components: {
|
|
||||||
main: lazyLoadView('Player'),
|
|
||||||
},
|
|
||||||
props: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/match/:match_id',
|
|
||||||
name: 'Match',
|
|
||||||
components: {
|
|
||||||
main: lazyLoadView('Match')
|
|
||||||
},
|
|
||||||
props: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
components: {
|
|
||||||
score: lazyLoadComponent('ScoreTeam')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'economy',
|
|
||||||
components: {
|
|
||||||
score: lazyLoadComponent('EqValueGraph')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'details',
|
|
||||||
components: {
|
|
||||||
score: lazyLoadComponent('Details')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'flashes',
|
|
||||||
components: {
|
|
||||||
score: lazyLoadComponent('FlashChart')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'damage',
|
|
||||||
components: {
|
|
||||||
score: lazyLoadComponent('DamageSite')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'chat',
|
|
||||||
components: {
|
|
||||||
score: lazyLoadComponent('MatchChatHistory')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/404',
|
|
||||||
name: '404',
|
|
||||||
components: {
|
|
||||||
main: lazyLoadErrorPages('404')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/500',
|
|
||||||
name: '500',
|
|
||||||
components: {
|
|
||||||
main: lazyLoadErrorPages('500')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/502',
|
|
||||||
name: '502',
|
|
||||||
components: {
|
|
||||||
main: lazyLoadErrorPages('502')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/:pathMatch(.*)*',
|
|
||||||
redirect: '/'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(process.env.BASE_URL),
|
|
||||||
routes,
|
|
||||||
scrollBehavior(to, from, savedPosition) {
|
|
||||||
if (savedPosition) {
|
|
||||||
return savedPosition
|
|
||||||
} else {
|
|
||||||
return {x: 0, y: 0}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
|
||||||
130
src/router/index.ts
Normal file
130
src/router/index.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
|
|
||||||
|
function lazyLoadView(view: string) {
|
||||||
|
return () => import(`../views/${view}.vue`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function lazyLoadErrorPages(view: string) {
|
||||||
|
return () => import(`../views/errorPages/${view}.vue`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function lazyLoadComponent(view: string) {
|
||||||
|
return () => import(`../components/${view}.vue`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
name: "Home",
|
||||||
|
components: {
|
||||||
|
main: lazyLoadView("HomeView"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/privacy-policy",
|
||||||
|
name: "PrivacyPolicy",
|
||||||
|
components: {
|
||||||
|
main: lazyLoadView("PrivacyPolicy"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/matches",
|
||||||
|
name: "Explore",
|
||||||
|
components: {
|
||||||
|
main: lazyLoadView("ExploreView"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/player/:id",
|
||||||
|
name: "Player",
|
||||||
|
components: {
|
||||||
|
main: lazyLoadView("PlayerView"),
|
||||||
|
},
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/match/:match_id",
|
||||||
|
name: "Match",
|
||||||
|
components: {
|
||||||
|
main: lazyLoadView("MatchView"),
|
||||||
|
},
|
||||||
|
props: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
components: {
|
||||||
|
score: lazyLoadComponent("ScoreTeam"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "economy",
|
||||||
|
components: {
|
||||||
|
score: lazyLoadComponent("EqValueGraph"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "details",
|
||||||
|
components: {
|
||||||
|
score: lazyLoadComponent("DetailsComponent"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "flashes",
|
||||||
|
components: {
|
||||||
|
score: lazyLoadComponent("FlashChart"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "damage",
|
||||||
|
components: {
|
||||||
|
score: lazyLoadComponent("DamageSite"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "chat",
|
||||||
|
components: {
|
||||||
|
score: lazyLoadComponent("MatchChatHistory"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/404",
|
||||||
|
name: "404",
|
||||||
|
components: {
|
||||||
|
main: lazyLoadErrorPages("404Page"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/500",
|
||||||
|
name: "500",
|
||||||
|
components: {
|
||||||
|
main: lazyLoadErrorPages("500Page"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/502",
|
||||||
|
name: "502",
|
||||||
|
components: {
|
||||||
|
main: lazyLoadErrorPages("502Page"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/:pathMatch(.*)*",
|
||||||
|
redirect: "/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
scrollBehavior(to, from, savedPosition) {
|
||||||
|
if (savedPosition) {
|
||||||
|
return savedPosition;
|
||||||
|
} else {
|
||||||
|
return { x: 0, y: 0 };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
cursor: help;
|
cursor: help;
|
||||||
text-decoration: underline dotted grey;
|
text-decoration: underline dotted grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
.helpicon {
|
.helpicon {
|
||||||
cursor: help;
|
cursor: help;
|
||||||
}
|
}
|
||||||
@@ -19,7 +20,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-wave-alt {
|
.placeholder-wave-alt {
|
||||||
mask-image: linear-gradient(130deg, black 55%, rgba(0, 0, 0, (1 - 0.2)) 75%, black 95%);
|
mask-image: linear-gradient(
|
||||||
|
130deg,
|
||||||
|
black 55%,
|
||||||
|
rgba(0, 0, 0, (1 - 0.2)) 75%,
|
||||||
|
black 95%
|
||||||
|
);
|
||||||
mask-size: 200% 100%;
|
mask-size: 200% 100%;
|
||||||
animation: placeholder-wave-alt 2.5s linear infinite;
|
animation: placeholder-wave-alt 2.5s linear infinite;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,38 +2,37 @@
|
|||||||
//@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');
|
//@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');
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "OpenSans";
|
font-family: "OpenSans";
|
||||||
src: local('OpenSans'),
|
src: local("OpenSans"),
|
||||||
url("/fonts/OpenSans-VariableFont_wdth,wght.woff2") format("woff2"),
|
url("/fonts/OpenSans-VariableFont_wdth,wght.woff2") format("woff2"),
|
||||||
url("/fonts/OpenSans-VariableFont_wdth,wght.ttf") format("truetype");
|
url("/fonts/OpenSans-VariableFont_wdth,wght.ttf") format("truetype");
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "OpenSansItalic";
|
font-family: "OpenSansItalic";
|
||||||
src: local('OpenSansItalic'),
|
src: local("OpenSansItalic"),
|
||||||
url("/fonts/OpenSans-Italic-VariableFont_wdth,wght.woff2") format("woff2"),
|
url("/fonts/OpenSans-Italic-VariableFont_wdth,wght.woff2") format("woff2"),
|
||||||
url("/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf") format("truetype");
|
url("/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf") format("truetype");
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "CSRegular";
|
font-family: "CSRegular";
|
||||||
src: local('CSRegular'),
|
src: local("CSRegular"), url("/fonts/cs_regular.woff2") format("woff2"),
|
||||||
url("/fonts/cs_regular.woff2") format("woff2"),
|
url("/fonts/cs_regular.ttf") format("truetype");
|
||||||
url("/fonts/cs_regular.ttf") format("truetype");
|
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Orbitron";
|
font-family: "Orbitron";
|
||||||
src: local('Orbitron'),
|
src: local("Orbitron"),
|
||||||
url("/fonts/Orbitron-VariableFont_wght.woff2") format("woff2"),
|
url("/fonts/Orbitron-VariableFont_wght.woff2") format("woff2"),
|
||||||
url("/fonts/Orbitron-VariableFont_wght.ttf") format("truetype");
|
url("/fonts/Orbitron-VariableFont_wght.ttf") format("truetype");
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default variable overrides
|
// Default variable overrides
|
||||||
$font-family-base: 'OpenSans';
|
$font-family-base: "OpenSans";
|
||||||
$body-color: white;
|
$body-color: white;
|
||||||
|
|
||||||
$primary: #888f98;
|
$primary: #888f98;
|
||||||
@@ -59,11 +58,11 @@ $success: #609926;
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
// CSGO COLORS
|
// CSGO COLORS
|
||||||
--csgo-orange: #FE9A28;
|
--csgo-orange: #fe9a28;
|
||||||
--csgo-blue: #5BA7FE;
|
--csgo-blue: #5ba7fe;
|
||||||
--csgo-yellow: #F7F52F;
|
--csgo-yellow: #f7f52f;
|
||||||
--csgo-purple: #A01BEF;
|
--csgo-purple: #a01bef;
|
||||||
--csgo-green: #04B462;
|
--csgo-green: #04b462;
|
||||||
--csgo-grey: #5a5a5a;
|
--csgo-grey: #5a5a5a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,66 +1,63 @@
|
|||||||
import { createStore } from 'vuex'
|
import { createStore } from "vuex";
|
||||||
|
|
||||||
export default createStore({
|
export default createStore({
|
||||||
state: {
|
state: {
|
||||||
id64: '',
|
id64: "",
|
||||||
vanityUrl: '',
|
vanityUrl: "",
|
||||||
matchDetails: {},
|
matchDetails: {},
|
||||||
playerDetails: {},
|
playerDetails: {},
|
||||||
playersArr: [],
|
playersArr: [],
|
||||||
scroll_state: 0,
|
scroll_state: 0,
|
||||||
info: []
|
info: [],
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
changeId64(state, payload) {
|
changeId64(state, payload) {
|
||||||
state.id64 = payload.id
|
state.id64 = payload.id;
|
||||||
},
|
},
|
||||||
changeVanityUrl(state, payload) {
|
changeVanityUrl(state, payload) {
|
||||||
state.vanityUrl = payload.id
|
state.vanityUrl = payload.id;
|
||||||
},
|
},
|
||||||
changeMatchDetails(state, payload) {
|
changeMatchDetails(state, payload) {
|
||||||
state.matchDetails = payload.data
|
state.matchDetails = payload.data;
|
||||||
},
|
},
|
||||||
changePlayerDetails(state, payload) {
|
changePlayerDetails(state, payload) {
|
||||||
state.playerDetails = payload.data
|
state.playerDetails = payload.data;
|
||||||
},
|
},
|
||||||
changePlayersArr(state, payload) {
|
changePlayersArr(state, payload) {
|
||||||
state.playersArr = payload.data
|
state.playersArr = payload.data;
|
||||||
},
|
},
|
||||||
changeScrollState(state, payload) {
|
changeScrollState(state, payload) {
|
||||||
state.scroll_state = payload
|
state.scroll_state = payload;
|
||||||
},
|
},
|
||||||
changeInfoState(state, payload) {
|
changeInfoState(state, payload) {
|
||||||
state.info.push(payload.data)
|
state.info.push(payload.data);
|
||||||
},
|
},
|
||||||
resetId64(state) {
|
resetId64(state) {
|
||||||
state.id64 = ''
|
state.id64 = "";
|
||||||
},
|
},
|
||||||
resetVanityUrl(state) {
|
resetVanityUrl(state) {
|
||||||
state.vanityUrl = ''
|
state.vanityUrl = "";
|
||||||
},
|
},
|
||||||
resetMatchDetails(state) {
|
resetMatchDetails(state) {
|
||||||
state.matchDetails = {}
|
state.matchDetails = {};
|
||||||
},
|
},
|
||||||
resetPlayerDetails(state) {
|
resetPlayerDetails(state) {
|
||||||
state.playerDetails = {}
|
state.playerDetails = {};
|
||||||
},
|
},
|
||||||
resetPlayersArr(state) {
|
resetPlayersArr(state) {
|
||||||
state.playersArr = []
|
state.playersArr = [];
|
||||||
},
|
},
|
||||||
resetScrollState(state) {
|
resetScrollState(state) {
|
||||||
state.scroll_state = 0
|
state.scroll_state = 0;
|
||||||
},
|
},
|
||||||
resetInfoState(state) {
|
resetInfoState(state) {
|
||||||
state.info = []
|
state.info = [];
|
||||||
},
|
},
|
||||||
removeInfoState(state, id) {
|
removeInfoState(state, id) {
|
||||||
state.info.splice(id, 1)
|
state.info.splice(id, 1);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {},
|
||||||
},
|
modules: {},
|
||||||
modules: {
|
getters: {},
|
||||||
},
|
});
|
||||||
getters: {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|||||||
32
src/stores/infoState.ts
Normal file
32
src/stores/infoState.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
export type infoState = {
|
||||||
|
statusCode: number;
|
||||||
|
message: string;
|
||||||
|
type: "error" | "success" | "warning";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RootState = {
|
||||||
|
infoState: infoState[];
|
||||||
|
infoCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useInfoStateStore = defineStore({
|
||||||
|
id: "infoState",
|
||||||
|
state: () =>
|
||||||
|
({
|
||||||
|
infoState: [],
|
||||||
|
infoCount: 0,
|
||||||
|
} as RootState),
|
||||||
|
actions: {
|
||||||
|
addInfo(payload: infoState) {
|
||||||
|
this.infoState.push(payload);
|
||||||
|
this.infoCount += 1;
|
||||||
|
},
|
||||||
|
removeInfo(id: number) {
|
||||||
|
this.infoState.splice(id, 1);
|
||||||
|
this.infoCount -= 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getters: {},
|
||||||
|
});
|
||||||
18
src/stores/matchDetails.ts
Normal file
18
src/stores/matchDetails.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
import type { Match, MatchChat } from "@/types";
|
||||||
|
|
||||||
|
export type RootState = {
|
||||||
|
matchDetails: Match;
|
||||||
|
matchChat: MatchChat;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useMatchDetailsStore = defineStore({
|
||||||
|
id: "matchDetails",
|
||||||
|
state: () =>
|
||||||
|
({
|
||||||
|
matchDetails: {},
|
||||||
|
matchChat: {},
|
||||||
|
} as RootState),
|
||||||
|
actions: {},
|
||||||
|
getters: {},
|
||||||
|
});
|
||||||
16
src/stores/playerDetails.ts
Normal file
16
src/stores/playerDetails.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
import type { Player } from "@/types";
|
||||||
|
|
||||||
|
export type RootState = {
|
||||||
|
playerDetails: Player;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usePlayerDetailsStore = defineStore({
|
||||||
|
id: "playerDetails",
|
||||||
|
state: () =>
|
||||||
|
({
|
||||||
|
playerDetails: {},
|
||||||
|
} as RootState),
|
||||||
|
actions: {},
|
||||||
|
getters: {},
|
||||||
|
});
|
||||||
16
src/stores/playersArr.ts
Normal file
16
src/stores/playersArr.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
import type { MatchStats } from "@/types";
|
||||||
|
|
||||||
|
export type RootState = {
|
||||||
|
playersArr: MatchStats[] | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usePlayersArrStore = defineStore({
|
||||||
|
id: "playersArr",
|
||||||
|
state: () =>
|
||||||
|
({
|
||||||
|
playersArr: [],
|
||||||
|
} as RootState),
|
||||||
|
actions: {},
|
||||||
|
getters: {},
|
||||||
|
});
|
||||||
15
src/stores/scrollState.ts
Normal file
15
src/stores/scrollState.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
export type RootState = {
|
||||||
|
scrollState: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useScrollStateStore = defineStore({
|
||||||
|
id: "scrollState",
|
||||||
|
state: () =>
|
||||||
|
({
|
||||||
|
scrollState: 0,
|
||||||
|
} as RootState),
|
||||||
|
actions: {},
|
||||||
|
getters: {},
|
||||||
|
});
|
||||||
19
src/stores/searchParams.ts
Normal file
19
src/stores/searchParams.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
export type RootState = {
|
||||||
|
id64: string;
|
||||||
|
vanityUrl: string;
|
||||||
|
shareCode: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSearchParamsStore = defineStore({
|
||||||
|
id: "searchParams",
|
||||||
|
state: () =>
|
||||||
|
({
|
||||||
|
id64: "",
|
||||||
|
vanityUrl: "",
|
||||||
|
shareCode: "",
|
||||||
|
} as RootState),
|
||||||
|
actions: {},
|
||||||
|
getters: {},
|
||||||
|
});
|
||||||
6
src/types/LocalStoragePlayer.ts
Normal file
6
src/types/LocalStoragePlayer.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export interface LocalStoragePlayer {
|
||||||
|
steamId64: string;
|
||||||
|
vanityUrl: string;
|
||||||
|
name: string;
|
||||||
|
avatar: string;
|
||||||
|
}
|
||||||
19
src/types/Match.ts
Normal file
19
src/types/Match.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { MatchStats } from "@/types/MatchStats";
|
||||||
|
|
||||||
|
export interface Match {
|
||||||
|
match_id: string;
|
||||||
|
share_code?: string;
|
||||||
|
map: string;
|
||||||
|
date: number;
|
||||||
|
score: [team_1: number, team_2: number];
|
||||||
|
duration: number;
|
||||||
|
match_result: number;
|
||||||
|
max_rounds?: number;
|
||||||
|
parsed: boolean;
|
||||||
|
replay_url?: string;
|
||||||
|
vac: boolean;
|
||||||
|
game_ban: boolean;
|
||||||
|
avg_rank?: number;
|
||||||
|
tick_rate?: number;
|
||||||
|
stats?: MatchStats[];
|
||||||
|
}
|
||||||
14
src/types/MatchChat.ts
Normal file
14
src/types/MatchChat.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import type { Player } from "@/types/Player";
|
||||||
|
|
||||||
|
export interface MatchChat {
|
||||||
|
[key: string]: MatchChatItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MatchChatItem {
|
||||||
|
player?: Player;
|
||||||
|
message: string;
|
||||||
|
all_chat: boolean;
|
||||||
|
tick: number;
|
||||||
|
translated_from?: string;
|
||||||
|
translated_to?: string;
|
||||||
|
}
|
||||||
5
src/types/MatchRounds.ts
Normal file
5
src/types/MatchRounds.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface MatchRounds {
|
||||||
|
[key: string]: {
|
||||||
|
[key: string]: number[];
|
||||||
|
};
|
||||||
|
}
|
||||||
40
src/types/MatchStats.ts
Normal file
40
src/types/MatchStats.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import type { Player } from "@/types/Player";
|
||||||
|
|
||||||
|
export interface MatchStats {
|
||||||
|
team_id: number;
|
||||||
|
kills: number;
|
||||||
|
deaths: number;
|
||||||
|
assists: number;
|
||||||
|
headshot: number;
|
||||||
|
mvp: number;
|
||||||
|
score: number;
|
||||||
|
rank?: {
|
||||||
|
old?: number;
|
||||||
|
new?: number;
|
||||||
|
};
|
||||||
|
multi_kills?: {
|
||||||
|
duo?: number;
|
||||||
|
triple?: number;
|
||||||
|
quad?: number;
|
||||||
|
pent?: number;
|
||||||
|
};
|
||||||
|
dmg?: {
|
||||||
|
enemy?: number;
|
||||||
|
team?: number;
|
||||||
|
};
|
||||||
|
flash?: {
|
||||||
|
duration?: {
|
||||||
|
self?: number;
|
||||||
|
team?: number;
|
||||||
|
enemy?: number;
|
||||||
|
};
|
||||||
|
total?: {
|
||||||
|
self?: number;
|
||||||
|
team?: number;
|
||||||
|
enemy?: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
crosshair?: string;
|
||||||
|
color?: string;
|
||||||
|
player?: Player;
|
||||||
|
}
|
||||||
19
src/types/MatchWeapons.ts
Normal file
19
src/types/MatchWeapons.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
export interface MatchWeapons {
|
||||||
|
equipment_map?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
stats?: [
|
||||||
|
{
|
||||||
|
[key: string]: {
|
||||||
|
[key: string]: [equip: number, spent: number, bank: number];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
spray?: [
|
||||||
|
{
|
||||||
|
[key: string]: {
|
||||||
|
[key: string]: [x: number, y: number];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
19
src/types/Player.ts
Normal file
19
src/types/Player.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { Match } from "@/types/Match";
|
||||||
|
|
||||||
|
export interface Player {
|
||||||
|
steamid64: string;
|
||||||
|
name?: string;
|
||||||
|
avatar?: string;
|
||||||
|
vac: boolean;
|
||||||
|
vac_date?: number;
|
||||||
|
game_ban: boolean;
|
||||||
|
game_ban_date?: number;
|
||||||
|
tracked: boolean;
|
||||||
|
vanity_url?: string;
|
||||||
|
match_stats?: {
|
||||||
|
win?: number;
|
||||||
|
loss?: number;
|
||||||
|
tie?: number;
|
||||||
|
};
|
||||||
|
matches?: Match[];
|
||||||
|
}
|
||||||
8
src/types/PlayerMate.ts
Normal file
8
src/types/PlayerMate.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import type { Player } from "@/types/Player";
|
||||||
|
|
||||||
|
export interface PlayerMate {
|
||||||
|
player: Player;
|
||||||
|
win_rate?: number;
|
||||||
|
tie_rate?: number;
|
||||||
|
total?: number;
|
||||||
|
}
|
||||||
26
src/types/PlayerMeta.ts
Normal file
26
src/types/PlayerMeta.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { Player } from "@/types/Player";
|
||||||
|
import type { PlayerMate } from "@/types/PlayerMate";
|
||||||
|
|
||||||
|
export interface PlayerMeta {
|
||||||
|
player: Player;
|
||||||
|
best_mates?: PlayerMate[];
|
||||||
|
most_mates?: PlayerMate[];
|
||||||
|
eq_map?: {
|
||||||
|
[key: number]: string;
|
||||||
|
};
|
||||||
|
weapon_dmg?: [
|
||||||
|
{
|
||||||
|
eq: number;
|
||||||
|
dmg: number;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
win_maps?: {
|
||||||
|
[key: string]: number;
|
||||||
|
};
|
||||||
|
tie_maps?: {
|
||||||
|
[key: string]: number;
|
||||||
|
};
|
||||||
|
total_maps?: {
|
||||||
|
[key: string]: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
9
src/types/index.ts
Normal file
9
src/types/index.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export * from "@/types/Match";
|
||||||
|
export * from "@/types/MatchChat";
|
||||||
|
export * from "@/types/MatchRounds";
|
||||||
|
export * from "@/types/MatchStats";
|
||||||
|
export * from "@/types/MatchWeapons";
|
||||||
|
export * from "@/types/Player";
|
||||||
|
export * from "@/types/PlayerMate";
|
||||||
|
export * from "@/types/PlayerMeta";
|
||||||
|
export * from "@/types/LocalStoragePlayer";
|
||||||
@@ -1,450 +0,0 @@
|
|||||||
import axios from "axios";
|
|
||||||
import {StatusCodes as STATUS} from "http-status-codes";
|
|
||||||
import {AUTHCODE_REGEX, SHARECODE_REGEX} from "@/constants";
|
|
||||||
|
|
||||||
const API_URL = process.env.VUE_APP_API_URL
|
|
||||||
|
|
||||||
// /player/<id> GET returns player <id> details (last 10 matches)
|
|
||||||
export const GetUser = async (store, id) => {
|
|
||||||
let response = null
|
|
||||||
|
|
||||||
await axios
|
|
||||||
.get(`${API_URL}/player/${id}`)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === STATUS.OK)
|
|
||||||
response = res.data
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
let message = ''
|
|
||||||
|
|
||||||
switch (err.response.status) {
|
|
||||||
case STATUS.BAD_REQUEST:
|
|
||||||
message = 'Bad request'
|
|
||||||
break
|
|
||||||
case STATUS.NOT_FOUND:
|
|
||||||
message = 'Player not found'
|
|
||||||
break
|
|
||||||
case STATUS.INTERNAL_SERVER_ERROR:
|
|
||||||
message = 'Unable to get meta-stats or player'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
message = 'An unknown error occurred'
|
|
||||||
}
|
|
||||||
store.commit({
|
|
||||||
type: 'changeInfoState',
|
|
||||||
data: {
|
|
||||||
statuscode: err.response.status,
|
|
||||||
message,
|
|
||||||
type: 'error'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
// /player/<id>/meta/<limit> GET returns player <id> meta-stats with <limit>
|
|
||||||
export const GetPlayerMeta = async (store, player_id, limit = 4) => {
|
|
||||||
let response = null
|
|
||||||
|
|
||||||
await axios
|
|
||||||
.get(`${API_URL}/player/${player_id}/meta/${limit}`)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === STATUS.OK)
|
|
||||||
response = res.data
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
let message = ''
|
|
||||||
|
|
||||||
switch (err.response.status) {
|
|
||||||
case STATUS.BAD_REQUEST:
|
|
||||||
message = 'Bad request'
|
|
||||||
break
|
|
||||||
case STATUS.NOT_FOUND:
|
|
||||||
message = 'Player not found'
|
|
||||||
break
|
|
||||||
case STATUS.INTERNAL_SERVER_ERROR:
|
|
||||||
message = 'Unable to get player meta'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
message = 'An unknown error occurred'
|
|
||||||
}
|
|
||||||
store.commit({
|
|
||||||
type: 'changeInfoState',
|
|
||||||
data: {
|
|
||||||
statuscode: err.response.status,
|
|
||||||
message,
|
|
||||||
type: 'error'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
// /player/<id>/next/<unix> GET returns 20 matches after <unix> for player <id>
|
|
||||||
export const LoadMoreMatches = async (store, player_id, date) => {
|
|
||||||
let response = null
|
|
||||||
|
|
||||||
await axios
|
|
||||||
.get(`${API_URL}/player/${player_id}/next/${date}`)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === STATUS.OK)
|
|
||||||
response = res.data
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
let message = ''
|
|
||||||
|
|
||||||
switch (err.response.status) {
|
|
||||||
case STATUS.BAD_REQUEST:
|
|
||||||
message = 'Bad request'
|
|
||||||
break
|
|
||||||
case STATUS.NOT_FOUND:
|
|
||||||
message = 'Player not found'
|
|
||||||
break
|
|
||||||
case STATUS.INTERNAL_SERVER_ERROR:
|
|
||||||
message = 'Unable to get meta-stats or player'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
message = 'An unknown error occurred'
|
|
||||||
}
|
|
||||||
store.commit({
|
|
||||||
type: 'changeInfoState',
|
|
||||||
data: {
|
|
||||||
statuscode: err.response.status,
|
|
||||||
message,
|
|
||||||
type: 'error'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
// /player/<id>/track POST Track player <id> FORM_DATA: authcode, [sharecode]
|
|
||||||
export const TrackMe = async (store, id64, authcode, sharecode = '') => {
|
|
||||||
let status = null
|
|
||||||
let message = ''
|
|
||||||
|
|
||||||
if (sharecode !== '' && !SHARECODE_REGEX.test(sharecode)) {
|
|
||||||
status = STATUS.IM_A_TEAPOT
|
|
||||||
message = 'Sharecode is invalid'
|
|
||||||
}
|
|
||||||
if (authcode === '' || !AUTHCODE_REGEX.test(authcode.toUpperCase())) {
|
|
||||||
status = STATUS.IM_A_TEAPOT
|
|
||||||
message = 'Authcode is invalid'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status === null && message === '') {
|
|
||||||
await axios
|
|
||||||
.post(`${API_URL}/player/${id64}/track`, `authcode=${authcode.toUpperCase()}&sharecode=${sharecode}`)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === STATUS.ACCEPTED) {
|
|
||||||
status = STATUS.ACCEPTED
|
|
||||||
message = 'Tracking successful'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
switch (err.response.status) {
|
|
||||||
case STATUS.BAD_REQUEST:
|
|
||||||
message = 'Invalid arguments'
|
|
||||||
break
|
|
||||||
case STATUS.NOT_FOUND:
|
|
||||||
message = 'Player not found'
|
|
||||||
break
|
|
||||||
case STATUS.SERVICE_UNAVAILABLE:
|
|
||||||
message = 'Service currently unavailable - Please try again later'
|
|
||||||
break
|
|
||||||
case STATUS.UNAUTHORIZED:
|
|
||||||
message = 'Authcode is invalid'
|
|
||||||
break
|
|
||||||
case STATUS.PRECONDITION_FAILED:
|
|
||||||
message = 'Sharecode is invalid or missing'
|
|
||||||
break
|
|
||||||
case STATUS.INTERNAL_SERVER_ERROR:
|
|
||||||
message = 'Service is currently unavailable - Please try again later'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
message = 'An unknown error occurred'
|
|
||||||
}
|
|
||||||
status = err.response.status
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
store.commit({
|
|
||||||
type: 'changeInfoState',
|
|
||||||
data: {
|
|
||||||
statuscode: status,
|
|
||||||
message,
|
|
||||||
type: 'error'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return status
|
|
||||||
}
|
|
||||||
|
|
||||||
// /match/<id> GET returns details for match <id>
|
|
||||||
export const GetMatchDetails = async (store, match_id) => {
|
|
||||||
let response = null
|
|
||||||
|
|
||||||
await axios
|
|
||||||
.get(`${API_URL}/match/${match_id}`)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === STATUS.OK)
|
|
||||||
response = res.data
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
let message = ''
|
|
||||||
|
|
||||||
switch (err.response.status) {
|
|
||||||
case STATUS.BAD_REQUEST:
|
|
||||||
message = 'Error parsing matchID'
|
|
||||||
break
|
|
||||||
case STATUS.NOT_FOUND:
|
|
||||||
message = 'Match not found'
|
|
||||||
break
|
|
||||||
case STATUS.INTERNAL_SERVER_ERROR:
|
|
||||||
message = 'Unable to get match data'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
message = 'An unknown error occurred'
|
|
||||||
}
|
|
||||||
store.commit({
|
|
||||||
type: 'changeInfoState',
|
|
||||||
data: {
|
|
||||||
statuscode: err.response.status,
|
|
||||||
message,
|
|
||||||
type: 'error'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
// /match/<id>/rounds GET returns round-stats for match <id>
|
|
||||||
export const GetPlayerValue = async (store, match_id) => {
|
|
||||||
let response = null
|
|
||||||
|
|
||||||
await axios
|
|
||||||
.get(`${API_URL}/match/${match_id}/rounds`)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === STATUS.OK)
|
|
||||||
response = res.data
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
let message = ''
|
|
||||||
|
|
||||||
switch (err.response.status) {
|
|
||||||
case STATUS.BAD_REQUEST:
|
|
||||||
message = 'Error parsing matchID'
|
|
||||||
break
|
|
||||||
case STATUS.NOT_FOUND:
|
|
||||||
message = 'Match not found'
|
|
||||||
break
|
|
||||||
case STATUS.INTERNAL_SERVER_ERROR:
|
|
||||||
message = 'Unable to get match data'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
message = 'An unknown error occurred'
|
|
||||||
}
|
|
||||||
store.commit({
|
|
||||||
type: 'changeInfoState',
|
|
||||||
data: {
|
|
||||||
statuscode: err.response.status,
|
|
||||||
message,
|
|
||||||
type: 'error'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
// /match/<id>/weapons GET returns weapon-stats for match <id>
|
|
||||||
export const GetWeaponDmg = async (store, match_id) => {
|
|
||||||
let response = null
|
|
||||||
|
|
||||||
await axios
|
|
||||||
.get(`${API_URL}/match/${match_id}/weapons`)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === STATUS.OK)
|
|
||||||
response = res.data
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
let message = ''
|
|
||||||
|
|
||||||
switch (err.response.status) {
|
|
||||||
case STATUS.BAD_REQUEST:
|
|
||||||
message = 'Bad request'
|
|
||||||
break
|
|
||||||
case STATUS.NOT_FOUND:
|
|
||||||
message = 'Weapon damage not found'
|
|
||||||
break
|
|
||||||
case STATUS.INTERNAL_SERVER_ERROR:
|
|
||||||
message = 'Unable to get weapon damage'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
message = 'An unknown error occurred'
|
|
||||||
}
|
|
||||||
store.commit({
|
|
||||||
type: 'changeInfoState',
|
|
||||||
data: {
|
|
||||||
statuscode: err.response.status,
|
|
||||||
message,
|
|
||||||
type: 'error'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
// /match/<id>/chat GET returns chat history for match <id>
|
|
||||||
export const GetChatHistory = async (store, match_id) => {
|
|
||||||
let response = null
|
|
||||||
|
|
||||||
await axios
|
|
||||||
.get(`${API_URL}/match/${match_id}/chat`)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === STATUS.OK)
|
|
||||||
response = res.data
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
let message = ''
|
|
||||||
|
|
||||||
switch (err.response.status) {
|
|
||||||
case STATUS.BAD_REQUEST:
|
|
||||||
message = 'Bad request'
|
|
||||||
break
|
|
||||||
case STATUS.NOT_FOUND:
|
|
||||||
message = 'Weapon damage not found'
|
|
||||||
break
|
|
||||||
case STATUS.INTERNAL_SERVER_ERROR:
|
|
||||||
message = 'Unable to get weapon damage'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
message = 'An unknown error occurred'
|
|
||||||
}
|
|
||||||
store.commit({
|
|
||||||
type: 'changeInfoState',
|
|
||||||
data: {
|
|
||||||
statuscode: err.response.status,
|
|
||||||
message,
|
|
||||||
type: 'error'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
// /matches/<id>/chat/<langCode> GET returns chat history for match <id> with translated sections
|
|
||||||
export const GetChatHistoryTranslated = async (store, match_id) => {
|
|
||||||
let response = null
|
|
||||||
|
|
||||||
await axios
|
|
||||||
.get(`${API_URL}/match/${match_id}/chat?translate=1`)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === STATUS.OK)
|
|
||||||
response = res.data
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
let message = ''
|
|
||||||
|
|
||||||
switch (err.response.status) {
|
|
||||||
case STATUS.BAD_REQUEST:
|
|
||||||
message = 'Bad request'
|
|
||||||
break
|
|
||||||
case STATUS.NOT_FOUND:
|
|
||||||
message = 'Chat was not found'
|
|
||||||
break
|
|
||||||
case STATUS.INTERNAL_SERVER_ERROR:
|
|
||||||
message = 'Unable to get chat'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
message = 'An unknown error occurred'
|
|
||||||
}
|
|
||||||
store.commit({
|
|
||||||
type: 'changeInfoState',
|
|
||||||
data: {
|
|
||||||
statuscode: err.response.status,
|
|
||||||
message,
|
|
||||||
type: 'error'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
// /matches GET returns last 20 matches in DB
|
|
||||||
export const GetMatches = async (store) => {
|
|
||||||
let response = null
|
|
||||||
|
|
||||||
await axios
|
|
||||||
.get(`${API_URL}/matches`)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === STATUS.OK)
|
|
||||||
response = res.data
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
let message = ''
|
|
||||||
|
|
||||||
switch (err.response.status) {
|
|
||||||
case STATUS.BAD_REQUEST:
|
|
||||||
message = 'Bad request'
|
|
||||||
break
|
|
||||||
case STATUS.INTERNAL_SERVER_ERROR:
|
|
||||||
message = 'Unable to marshal JSON'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
message = 'An unknown error occurred'
|
|
||||||
}
|
|
||||||
store.commit({
|
|
||||||
type: 'changeInfoState',
|
|
||||||
data: {
|
|
||||||
statuscode: err.response.status,
|
|
||||||
message,
|
|
||||||
type: 'error'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
// /matches/next/<unix> GET returns 20 matches after time <unix>
|
|
||||||
export const LoadMoreMatchesExplore = async (store, date) => {
|
|
||||||
let response = null
|
|
||||||
|
|
||||||
await axios
|
|
||||||
.get(`${API_URL}/matches/next/${date}`)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === STATUS.OK)
|
|
||||||
response = res.data
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
let message = ''
|
|
||||||
|
|
||||||
switch (err.response.status) {
|
|
||||||
case STATUS.BAD_REQUEST:
|
|
||||||
message = 'Bad request'
|
|
||||||
break
|
|
||||||
case STATUS.INTERNAL_SERVER_ERROR:
|
|
||||||
message = 'Unable to load more matches'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
message = 'An unknown error occurred'
|
|
||||||
}
|
|
||||||
store.commit({
|
|
||||||
type: 'changeInfoState',
|
|
||||||
data: {
|
|
||||||
statuscode: err.response.status,
|
|
||||||
message,
|
|
||||||
type: 'error'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
485
src/utils/ApiRequests.ts
Normal file
485
src/utils/ApiRequests.ts
Normal file
@@ -0,0 +1,485 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { StatusCodes as STATUS } from "http-status-codes";
|
||||||
|
import { AUTHCODE_REGEX, SHARECODE_REGEX } from "@/constants";
|
||||||
|
import type {
|
||||||
|
Match,
|
||||||
|
MatchChat,
|
||||||
|
MatchRounds,
|
||||||
|
MatchWeapons,
|
||||||
|
Player,
|
||||||
|
PlayerMeta,
|
||||||
|
} from "@/types";
|
||||||
|
import type { infoState } from "@/stores/infoState";
|
||||||
|
import { reactive } from "vue";
|
||||||
|
|
||||||
|
const API_URL = import.meta.env.VITE_API_URL;
|
||||||
|
|
||||||
|
// /player/<id> GET returns player <id> details (last 10 matches)
|
||||||
|
export const GetUser = async (
|
||||||
|
id: string
|
||||||
|
): Promise<[Player | null, infoState]> => {
|
||||||
|
let response: Player | null = null;
|
||||||
|
const info = reactive<infoState>({
|
||||||
|
statusCode: 0,
|
||||||
|
message: "",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
await axios
|
||||||
|
.get(`${API_URL}/player/${id}`)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === STATUS.OK) response = res.data;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
switch (err.response.status) {
|
||||||
|
case STATUS.BAD_REQUEST:
|
||||||
|
info.message = "Bad request";
|
||||||
|
break;
|
||||||
|
case STATUS.NOT_FOUND:
|
||||||
|
info.message = "Player not found";
|
||||||
|
break;
|
||||||
|
case STATUS.INTERNAL_SERVER_ERROR:
|
||||||
|
info.message = "Unable to get meta-stats or player";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
info.message = "An unknown error occurred";
|
||||||
|
}
|
||||||
|
info.statusCode = err.response.status;
|
||||||
|
info.type = "error";
|
||||||
|
});
|
||||||
|
|
||||||
|
return [response, info];
|
||||||
|
};
|
||||||
|
|
||||||
|
// /player/<id>/meta/<limit> GET returns player <id> meta-stats with <limit>
|
||||||
|
export const GetPlayerMeta = async (
|
||||||
|
player_id: string,
|
||||||
|
limit = 4
|
||||||
|
): Promise<[PlayerMeta | null, infoState]> => {
|
||||||
|
let response: PlayerMeta | null = null;
|
||||||
|
const info = reactive<infoState>({
|
||||||
|
statusCode: 0,
|
||||||
|
message: "",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
await axios
|
||||||
|
.get(`${API_URL}/player/${player_id}/meta/${limit}`)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === STATUS.OK) response = res.data;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
switch (err.response.status) {
|
||||||
|
case STATUS.BAD_REQUEST:
|
||||||
|
info.message = "Bad request";
|
||||||
|
break;
|
||||||
|
case STATUS.NOT_FOUND:
|
||||||
|
info.message = "Player not found";
|
||||||
|
break;
|
||||||
|
case STATUS.INTERNAL_SERVER_ERROR:
|
||||||
|
info.message = "Unable to get player meta";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
info.message = "An unknown error occurred";
|
||||||
|
}
|
||||||
|
info.statusCode = err.response.status;
|
||||||
|
info.type = "error";
|
||||||
|
});
|
||||||
|
|
||||||
|
return [response, info];
|
||||||
|
};
|
||||||
|
|
||||||
|
// /player/<id>/next/<unix> GET returns 20 matches after <unix> for player <id>
|
||||||
|
export const LoadMoreMatches = async (
|
||||||
|
player_id: string,
|
||||||
|
date: number
|
||||||
|
): Promise<[Player | null, infoState]> => {
|
||||||
|
let response: Player | null = null;
|
||||||
|
const info = reactive<infoState>({
|
||||||
|
statusCode: 0,
|
||||||
|
message: "",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
await axios
|
||||||
|
.get(`${API_URL}/player/${player_id}/next/${date}`)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === STATUS.OK) response = res.data;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
switch (err.response.status) {
|
||||||
|
case STATUS.BAD_REQUEST:
|
||||||
|
info.message = "Bad request";
|
||||||
|
break;
|
||||||
|
case STATUS.NOT_FOUND:
|
||||||
|
info.message = "Player not found";
|
||||||
|
break;
|
||||||
|
case STATUS.INTERNAL_SERVER_ERROR:
|
||||||
|
info.message = "Unable to get meta-stats or player";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
info.message = "An unknown error occurred";
|
||||||
|
}
|
||||||
|
info.statusCode = err.response.status;
|
||||||
|
info.type = "error";
|
||||||
|
});
|
||||||
|
|
||||||
|
return [response, info];
|
||||||
|
};
|
||||||
|
|
||||||
|
// /player/<id>/track POST Track player <id> FORM_DATA: authcode, [sharecode]
|
||||||
|
export const TrackMe = async (
|
||||||
|
id64: string,
|
||||||
|
authcode: string,
|
||||||
|
sharecode = ""
|
||||||
|
): Promise<infoState> => {
|
||||||
|
const info = reactive<infoState>({
|
||||||
|
statusCode: 0,
|
||||||
|
message: "",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sharecode !== "" && !SHARECODE_REGEX.test(sharecode)) {
|
||||||
|
info.statusCode = STATUS.IM_A_TEAPOT;
|
||||||
|
info.message = "Sharecode is invalid";
|
||||||
|
info.type = "error";
|
||||||
|
}
|
||||||
|
if (authcode === "" || !AUTHCODE_REGEX.test(authcode.toUpperCase())) {
|
||||||
|
info.statusCode = STATUS.IM_A_TEAPOT;
|
||||||
|
info.message = "Authcode is invalid";
|
||||||
|
info.type = "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.statusCode === 0 && info.message === "") {
|
||||||
|
await axios
|
||||||
|
.post(
|
||||||
|
`${API_URL}/player/${id64}/track`,
|
||||||
|
`authcode=${authcode.toUpperCase()}&sharecode=${sharecode}`
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === STATUS.ACCEPTED) {
|
||||||
|
info.statusCode = STATUS.ACCEPTED;
|
||||||
|
info.message = "Tracking successful";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
switch (err.response.status) {
|
||||||
|
case STATUS.BAD_REQUEST:
|
||||||
|
info.message = "Invalid arguments";
|
||||||
|
break;
|
||||||
|
case STATUS.NOT_FOUND:
|
||||||
|
info.message = "Player not found";
|
||||||
|
break;
|
||||||
|
case STATUS.SERVICE_UNAVAILABLE:
|
||||||
|
info.message =
|
||||||
|
"Service currently unavailable - Please try again later";
|
||||||
|
break;
|
||||||
|
case STATUS.UNAUTHORIZED:
|
||||||
|
info.message = "Authcode is invalid";
|
||||||
|
break;
|
||||||
|
case STATUS.PRECONDITION_FAILED:
|
||||||
|
info.message = "Sharecode is invalid or missing";
|
||||||
|
break;
|
||||||
|
case STATUS.INTERNAL_SERVER_ERROR:
|
||||||
|
info.message =
|
||||||
|
"Service is currently unavailable - Please try again later";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
info.message = "An unknown error occurred";
|
||||||
|
}
|
||||||
|
info.statusCode = err.response.status;
|
||||||
|
info.type = "error";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
};
|
||||||
|
|
||||||
|
// /match/<id> GET returns details for match <id>
|
||||||
|
export const GetMatchDetails = async (
|
||||||
|
match_id: string
|
||||||
|
): Promise<[Match | null, infoState]> => {
|
||||||
|
let response: Match | null = null;
|
||||||
|
const info = reactive<infoState>({
|
||||||
|
statusCode: 0,
|
||||||
|
message: "",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
await axios
|
||||||
|
.get(`${API_URL}/match/${match_id}`)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === STATUS.OK) response = res.data;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
switch (err.response.status) {
|
||||||
|
case STATUS.BAD_REQUEST:
|
||||||
|
info.message = "Error parsing matchID";
|
||||||
|
break;
|
||||||
|
case STATUS.NOT_FOUND:
|
||||||
|
info.message = "Match not found";
|
||||||
|
break;
|
||||||
|
case STATUS.INTERNAL_SERVER_ERROR:
|
||||||
|
info.message = "Unable to get match data";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
info.message = "An unknown error occurred";
|
||||||
|
}
|
||||||
|
info.statusCode = err.response.status;
|
||||||
|
info.type = "error";
|
||||||
|
});
|
||||||
|
|
||||||
|
return [response, info];
|
||||||
|
};
|
||||||
|
|
||||||
|
// /match/<id>/rounds GET returns round-stats for match <id>
|
||||||
|
export const GetPlayerValue = async (
|
||||||
|
match_id: string
|
||||||
|
): Promise<[MatchRounds | null, infoState]> => {
|
||||||
|
let response: MatchRounds | null = null;
|
||||||
|
const info = reactive<infoState>({
|
||||||
|
statusCode: 0,
|
||||||
|
message: "",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
await axios
|
||||||
|
.get(`${API_URL}/match/${match_id}/rounds`)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === STATUS.OK) response = res.data;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
switch (err.response.status) {
|
||||||
|
case STATUS.BAD_REQUEST:
|
||||||
|
info.message = "Error parsing matchID";
|
||||||
|
break;
|
||||||
|
case STATUS.NOT_FOUND:
|
||||||
|
info.message = "Match not found";
|
||||||
|
break;
|
||||||
|
case STATUS.INTERNAL_SERVER_ERROR:
|
||||||
|
info.message = "Unable to get match data";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
info.message = "An unknown error occurred";
|
||||||
|
}
|
||||||
|
info.statusCode = err.response.status;
|
||||||
|
info.type = "error";
|
||||||
|
});
|
||||||
|
|
||||||
|
return [response, info];
|
||||||
|
};
|
||||||
|
|
||||||
|
// /match/<id>/weapons GET returns weapon-stats for match <id>
|
||||||
|
export const GetWeaponDmg = async (
|
||||||
|
match_id: string
|
||||||
|
): Promise<[MatchWeapons | null, infoState]> => {
|
||||||
|
let response: MatchWeapons | null = null;
|
||||||
|
const info = reactive<infoState>({
|
||||||
|
statusCode: 0,
|
||||||
|
message: "",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
await axios
|
||||||
|
.get(`${API_URL}/match/${match_id}/weapons`)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === STATUS.OK) response = res.data;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
switch (err.response.status) {
|
||||||
|
case STATUS.BAD_REQUEST:
|
||||||
|
info.message = "Bad request";
|
||||||
|
break;
|
||||||
|
case STATUS.NOT_FOUND:
|
||||||
|
info.message = "Weapon damage not found";
|
||||||
|
break;
|
||||||
|
case STATUS.INTERNAL_SERVER_ERROR:
|
||||||
|
info.message = "Unable to get weapon damage";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
info.message = "An unknown error occurred";
|
||||||
|
}
|
||||||
|
info.statusCode = err.response.status;
|
||||||
|
info.type = "error";
|
||||||
|
});
|
||||||
|
|
||||||
|
return [response, info];
|
||||||
|
};
|
||||||
|
|
||||||
|
// /match/<id>/chat GET returns chat history for match <id>
|
||||||
|
export const GetChatHistory = async (
|
||||||
|
match_id: string
|
||||||
|
): Promise<[MatchChat | null, infoState]> => {
|
||||||
|
let response: MatchChat | null = null;
|
||||||
|
const info = reactive<infoState>({
|
||||||
|
statusCode: 0,
|
||||||
|
message: "",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
await axios
|
||||||
|
.get(`${API_URL}/match/${match_id}/chat`)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === STATUS.OK) response = res.data;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
switch (err.response.status) {
|
||||||
|
case STATUS.BAD_REQUEST:
|
||||||
|
info.message = "Bad request";
|
||||||
|
break;
|
||||||
|
case STATUS.NOT_FOUND:
|
||||||
|
info.message = "Weapon damage not found";
|
||||||
|
break;
|
||||||
|
case STATUS.INTERNAL_SERVER_ERROR:
|
||||||
|
info.message = "Unable to get weapon damage";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
info.message = "An unknown error occurred";
|
||||||
|
}
|
||||||
|
info.statusCode = err.response.status;
|
||||||
|
info.type = "error";
|
||||||
|
});
|
||||||
|
|
||||||
|
return [response, info];
|
||||||
|
};
|
||||||
|
|
||||||
|
// /matches/<id>/chat/<langCode> GET returns chat history for match <id> with translated sections
|
||||||
|
export const GetChatHistoryTranslated = async (
|
||||||
|
match_id: string
|
||||||
|
): Promise<[MatchChat | null, infoState]> => {
|
||||||
|
let response: MatchChat | null = null;
|
||||||
|
const info = reactive<infoState>({
|
||||||
|
statusCode: 0,
|
||||||
|
message: "",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
await axios
|
||||||
|
.get(`${API_URL}/match/${match_id}/chat?translate=1`)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === STATUS.OK) response = res.data;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
switch (err.response.status) {
|
||||||
|
case STATUS.BAD_REQUEST:
|
||||||
|
info.message = "Bad request";
|
||||||
|
break;
|
||||||
|
case STATUS.NOT_FOUND:
|
||||||
|
info.message = "Chat was not found";
|
||||||
|
break;
|
||||||
|
case STATUS.INTERNAL_SERVER_ERROR:
|
||||||
|
info.message = "Unable to get chat";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
info.message = "An unknown error occurred";
|
||||||
|
}
|
||||||
|
info.statusCode = err.response.status;
|
||||||
|
info.type = "error";
|
||||||
|
});
|
||||||
|
|
||||||
|
return [response, info];
|
||||||
|
};
|
||||||
|
|
||||||
|
// /matches GET returns last 20 matches in DB
|
||||||
|
export const GetMatches = async (): Promise<[Match[] | null, infoState]> => {
|
||||||
|
let response: Match[] | null = null;
|
||||||
|
const info = reactive<infoState>({
|
||||||
|
statusCode: 0,
|
||||||
|
message: "",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
await axios
|
||||||
|
.get(`${API_URL}/matches`)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === STATUS.OK) response = res.data;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
switch (err.response.status) {
|
||||||
|
case STATUS.BAD_REQUEST:
|
||||||
|
info.message = "Bad request";
|
||||||
|
break;
|
||||||
|
case STATUS.INTERNAL_SERVER_ERROR:
|
||||||
|
info.message = "Unable to marshal JSON";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
info.message = "An unknown error occurred";
|
||||||
|
}
|
||||||
|
info.statusCode = err.response.status;
|
||||||
|
info.type = "error";
|
||||||
|
});
|
||||||
|
return [response, info];
|
||||||
|
};
|
||||||
|
|
||||||
|
// /matches/next/<unix> GET returns 20 matches after time <unix>
|
||||||
|
export const LoadMoreMatchesExplore = async (
|
||||||
|
date: number
|
||||||
|
): Promise<[Match[] | null, infoState]> => {
|
||||||
|
let response: Match[] | null = null;
|
||||||
|
const info = reactive<infoState>({
|
||||||
|
statusCode: 0,
|
||||||
|
message: "",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
await axios
|
||||||
|
.get(`${API_URL}/matches/next/${date}`)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === STATUS.OK) response = res.data;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
switch (err.response.status) {
|
||||||
|
case STATUS.BAD_REQUEST:
|
||||||
|
info.message = "Bad request";
|
||||||
|
break;
|
||||||
|
case STATUS.INTERNAL_SERVER_ERROR:
|
||||||
|
info.message = "Unable to load more matches";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
info.message = "An unknown error occurred";
|
||||||
|
}
|
||||||
|
info.statusCode = err.response.status;
|
||||||
|
info.type = "error";
|
||||||
|
});
|
||||||
|
|
||||||
|
return [response, info];
|
||||||
|
};
|
||||||
|
|
||||||
|
// /match/parse/<shareCode>
|
||||||
|
export const ParseMatch = async (shareCode: string): Promise<infoState> => {
|
||||||
|
const info = reactive<infoState>({
|
||||||
|
statusCode: 0,
|
||||||
|
message: "",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
await axios
|
||||||
|
.get(`${API_URL}/match/parse/${shareCode}`)
|
||||||
|
.then((res) => {
|
||||||
|
switch (res.status) {
|
||||||
|
case STATUS.OK:
|
||||||
|
info.statusCode = STATUS.OK;
|
||||||
|
info.message = "";
|
||||||
|
break;
|
||||||
|
case STATUS.ACCEPTED:
|
||||||
|
info.statusCode = STATUS.ACCEPTED;
|
||||||
|
info.message = "Match will be parsed";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
switch (err.response.status) {
|
||||||
|
case STATUS.BAD_REQUEST:
|
||||||
|
info.message = "Bad request";
|
||||||
|
break;
|
||||||
|
case STATUS.SERVICE_UNAVAILABLE:
|
||||||
|
info.message = "Unable to parse match";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
info.message = "An unknown error occurred";
|
||||||
|
}
|
||||||
|
info.statusCode = err.response.status;
|
||||||
|
info.type = "error";
|
||||||
|
});
|
||||||
|
|
||||||
|
return info;
|
||||||
|
};
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import {DateTime, Duration} from "luxon/build/es6/luxon";
|
|
||||||
|
|
||||||
export const ConvertTickToTime = (tick, rate = 64) => {
|
|
||||||
const time = Duration.fromObject({hours: 0, minutes: 0, seconds: tick / rate || 0})
|
|
||||||
|
|
||||||
if (time.hours > 1)
|
|
||||||
return time.toFormat('hh:mm:ss')
|
|
||||||
else if (time.hours < 1)
|
|
||||||
return time.toFormat('mm:ss')
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FormatDuration = (d) => {
|
|
||||||
const duration = Duration.fromObject({hours: 0, minutes: 0, seconds: d}).normalize().toObject()
|
|
||||||
|
|
||||||
if (duration.hours > 1)
|
|
||||||
return `${duration.hours} h ${duration.minutes} min`
|
|
||||||
else if (duration.hours < 1)
|
|
||||||
return `${duration.minutes} min`
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FormatFullDuration = (d) => {
|
|
||||||
const duration = Duration.fromObject({hours: 0, minutes: 0, seconds: d}).normalize()
|
|
||||||
|
|
||||||
if (duration.hours > 1)
|
|
||||||
return duration.toFormat('hh:mm:ss')
|
|
||||||
else if (duration.hours < 1)
|
|
||||||
return duration.toFormat('mm:ss')
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FormatDate = (date) => {
|
|
||||||
const matchDate = DateTime.fromSeconds(date || 0)
|
|
||||||
const diff = DateTime.now().diff(matchDate)
|
|
||||||
|
|
||||||
if (diff.as('days') > 8)
|
|
||||||
return matchDate.toLocaleString({weekday: 'short', day: '2-digit', month: '2-digit', year: 'numeric'})
|
|
||||||
else
|
|
||||||
return matchDate.toRelative()
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FormatFullDate = (date) => {
|
|
||||||
const matchDate = DateTime.fromSeconds(date || 0)
|
|
||||||
|
|
||||||
return matchDate.toLocaleString({
|
|
||||||
weekday: 'short',
|
|
||||||
day: '2-digit',
|
|
||||||
month: '2-digit',
|
|
||||||
year: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
second: '2-digit'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FormatVacDate = (date, match) => {
|
|
||||||
const vacDate = DateTime.fromSeconds(date || 0)
|
|
||||||
const matchDate = DateTime.fromSeconds(match || 0)
|
|
||||||
|
|
||||||
if (vacDate.diff(matchDate).as('days') >= -30) {
|
|
||||||
return vacDate.toRelative()
|
|
||||||
} else {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MatchNotParsedTime = (match) => {
|
|
||||||
const matchDate = DateTime.fromSeconds(match || 0)
|
|
||||||
|
|
||||||
return matchDate.diffNow().as('hours') >= -2;
|
|
||||||
}
|
|
||||||
86
src/utils/DateTime.ts
Normal file
86
src/utils/DateTime.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { DateTime, Duration } from "luxon";
|
||||||
|
|
||||||
|
export const ConvertTickToTime = (
|
||||||
|
tick: number,
|
||||||
|
rate = 64
|
||||||
|
): string | undefined => {
|
||||||
|
const time = Duration.fromObject({
|
||||||
|
hours: 0,
|
||||||
|
minutes: 0,
|
||||||
|
seconds: tick / rate || 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (time.hours > 1) return time.toFormat("hh:mm:ss");
|
||||||
|
else if (time.hours < 1) return time.toFormat("mm:ss");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormatDuration = (d: number): string | undefined => {
|
||||||
|
const duration = Duration.fromObject({
|
||||||
|
hours: 0,
|
||||||
|
minutes: 0,
|
||||||
|
seconds: d,
|
||||||
|
}).normalize();
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
if (duration.hours > 1) return `${duration.hours} h ${duration.minutes} min`;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
else if (duration.hours < 1) return `${duration.minutes} min`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormatFullDuration = (d: number): string | undefined => {
|
||||||
|
const duration = Duration.fromObject({
|
||||||
|
hours: 0,
|
||||||
|
minutes: 0,
|
||||||
|
seconds: d,
|
||||||
|
}).normalize();
|
||||||
|
|
||||||
|
if (duration.hours > 1) return duration.toFormat("hh:mm:ss");
|
||||||
|
else if (duration.hours < 1) return duration.toFormat("mm:ss");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormatDate = (date: number): string => {
|
||||||
|
const matchDate = DateTime.fromSeconds(date || 0);
|
||||||
|
const diff = DateTime.now().diff(matchDate);
|
||||||
|
|
||||||
|
if (diff.as("days") > 8)
|
||||||
|
return matchDate.toLocaleString({
|
||||||
|
weekday: "short",
|
||||||
|
day: "2-digit",
|
||||||
|
month: "2-digit",
|
||||||
|
year: "numeric",
|
||||||
|
});
|
||||||
|
else return matchDate.toRelative() || "";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormatFullDate = (date: number): string => {
|
||||||
|
const matchDate = DateTime.fromSeconds(date || 0);
|
||||||
|
|
||||||
|
return matchDate.toLocaleString({
|
||||||
|
weekday: "short",
|
||||||
|
day: "2-digit",
|
||||||
|
month: "2-digit",
|
||||||
|
year: "numeric",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormatVacDate = (date: number, match: number): string => {
|
||||||
|
const vacDate = DateTime.fromSeconds(date || 0);
|
||||||
|
const matchDate = DateTime.fromSeconds(match || 0);
|
||||||
|
|
||||||
|
if (vacDate.diff(matchDate).as("days") >= -30) {
|
||||||
|
return vacDate.toRelative() || "";
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MatchNotParsedTime = (match: number): boolean => {
|
||||||
|
const matchDate = DateTime.fromSeconds(match || 0);
|
||||||
|
|
||||||
|
return matchDate.diffNow().as("hours") >= -2;
|
||||||
|
};
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
export const DisplayRank = (rankNr = 0) => {
|
|
||||||
const rankMap = new Map([
|
|
||||||
[0, 'Unranked'],
|
|
||||||
[1, 'Silver I'],
|
|
||||||
[2, 'Silver II'],
|
|
||||||
[3, 'Silver III'],
|
|
||||||
[4, 'Silver IV'],
|
|
||||||
[5, 'Silver Elite'],
|
|
||||||
[6, 'Silver Elite Master'],
|
|
||||||
[7, 'Gold Nova I'],
|
|
||||||
[8, 'Gold Nova II'],
|
|
||||||
[9, 'Gold Nova III'],
|
|
||||||
[10, 'Gold Nova IV'],
|
|
||||||
[11, 'Master Guardian I'],
|
|
||||||
[12, 'Master Guardian II'],
|
|
||||||
[13, 'Master Guardian Elite'],
|
|
||||||
[14, 'Distinguished Master Guardian'],
|
|
||||||
[15, 'Legendary Eagle'],
|
|
||||||
[16, 'Legendary Eagle Master'],
|
|
||||||
[17, 'Supreme Master First Class'],
|
|
||||||
[18, 'Global Elite'],
|
|
||||||
])
|
|
||||||
return [`/images/rank_icons/skillgroup${rankNr}.svg`, rankMap.get(rankNr)]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DisplayWeapon = (weaponId) => {
|
|
||||||
const wepaonMap = new Map([
|
|
||||||
[1, 'p2000'],
|
|
||||||
[2, 'glock'],
|
|
||||||
[3, 'p250'],
|
|
||||||
[4, 'deagle'],
|
|
||||||
[5, 'fiveseven'],
|
|
||||||
[6, 'elite'],
|
|
||||||
[7, 'tec9'],
|
|
||||||
[8, 'cz75a'],
|
|
||||||
[9, 'usp_silencer'],
|
|
||||||
[10, 'revolver'],
|
|
||||||
|
|
||||||
[101, 'mp7'],
|
|
||||||
[102, 'mp9'],
|
|
||||||
[103, 'bizon'],
|
|
||||||
[104, 'mac10'],
|
|
||||||
[105, 'ump45'],
|
|
||||||
[106, 'p90'],
|
|
||||||
[107, 'mp5sd'],
|
|
||||||
|
|
||||||
[201, 'sawedoff'],
|
|
||||||
[202, 'nova'],
|
|
||||||
[203, 'mag7'],
|
|
||||||
[204, 'xm1014'],
|
|
||||||
[205, 'm249'],
|
|
||||||
[206, 'negev'],
|
|
||||||
|
|
||||||
[301, 'galilar'],
|
|
||||||
[302, 'famas'],
|
|
||||||
[303, 'ak47'],
|
|
||||||
[304, 'm4a1'],
|
|
||||||
[305, 'm4a1_silencer'],
|
|
||||||
[306, 'ssg08'],
|
|
||||||
[307, 'sg556'],
|
|
||||||
[308, 'aug'],
|
|
||||||
[309, 'awp'],
|
|
||||||
[310, 'scar20'],
|
|
||||||
[311, 'g3sg1'],
|
|
||||||
])
|
|
||||||
if (wepaonMap.get(weaponId)){
|
|
||||||
return `/images/weapons/${wepaonMap.get(weaponId)}.svg`
|
|
||||||
} else {
|
|
||||||
weaponId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LoadImage = (mapName) => {
|
|
||||||
let img = new Image()
|
|
||||||
let background = document.querySelector('.bg-img')
|
|
||||||
|
|
||||||
img.onload = function() {
|
|
||||||
if (background) {
|
|
||||||
background.src = img.src
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
img.onerror = function () {
|
|
||||||
img.src = `/images/map_screenshots/${mapName}.jpg`
|
|
||||||
img.onerror = null
|
|
||||||
}
|
|
||||||
|
|
||||||
img.src = `/images/map_screenshots/${mapName}.webp`
|
|
||||||
}
|
|
||||||
85
src/utils/Display.ts
Normal file
85
src/utils/Display.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
export const DisplayRank = (rankNr = 0): Array<string | undefined> => {
|
||||||
|
const rankMap = new Map([
|
||||||
|
[0, "Unranked"],
|
||||||
|
[1, "Silver I"],
|
||||||
|
[2, "Silver II"],
|
||||||
|
[3, "Silver III"],
|
||||||
|
[4, "Silver IV"],
|
||||||
|
[5, "Silver Elite"],
|
||||||
|
[6, "Silver Elite Master"],
|
||||||
|
[7, "Gold Nova I"],
|
||||||
|
[8, "Gold Nova II"],
|
||||||
|
[9, "Gold Nova III"],
|
||||||
|
[10, "Gold Nova IV"],
|
||||||
|
[11, "Master Guardian I"],
|
||||||
|
[12, "Master Guardian II"],
|
||||||
|
[13, "Master Guardian Elite"],
|
||||||
|
[14, "Distinguished Master Guardian"],
|
||||||
|
[15, "Legendary Eagle"],
|
||||||
|
[16, "Legendary Eagle Master"],
|
||||||
|
[17, "Supreme Master First Class"],
|
||||||
|
[18, "Global Elite"],
|
||||||
|
]);
|
||||||
|
return [`/images/rank_icons/skillgroup${rankNr}.svg`, rankMap.get(rankNr)];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DisplayWeapon = (weaponId: number): string => {
|
||||||
|
const wepaonMap = new Map([
|
||||||
|
[1, "p2000"],
|
||||||
|
[2, "glock"],
|
||||||
|
[3, "p250"],
|
||||||
|
[4, "deagle"],
|
||||||
|
[5, "fiveseven"],
|
||||||
|
[6, "elite"],
|
||||||
|
[7, "tec9"],
|
||||||
|
[8, "cz75a"],
|
||||||
|
[9, "usp_silencer"],
|
||||||
|
[10, "revolver"],
|
||||||
|
|
||||||
|
[101, "mp7"],
|
||||||
|
[102, "mp9"],
|
||||||
|
[103, "bizon"],
|
||||||
|
[104, "mac10"],
|
||||||
|
[105, "ump45"],
|
||||||
|
[106, "p90"],
|
||||||
|
[107, "mp5sd"],
|
||||||
|
|
||||||
|
[201, "sawedoff"],
|
||||||
|
[202, "nova"],
|
||||||
|
[203, "mag7"],
|
||||||
|
[204, "xm1014"],
|
||||||
|
[205, "m249"],
|
||||||
|
[206, "negev"],
|
||||||
|
|
||||||
|
[301, "galilar"],
|
||||||
|
[302, "famas"],
|
||||||
|
[303, "ak47"],
|
||||||
|
[304, "m4a1"],
|
||||||
|
[305, "m4a1_silencer"],
|
||||||
|
[306, "ssg08"],
|
||||||
|
[307, "sg556"],
|
||||||
|
[308, "aug"],
|
||||||
|
[309, "awp"],
|
||||||
|
[310, "scar20"],
|
||||||
|
[311, "g3sg1"],
|
||||||
|
]);
|
||||||
|
return `/images/weapons/${wepaonMap.get(weaponId) || ""}.svg`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LoadImage = (mapName: string): void => {
|
||||||
|
const img = new Image();
|
||||||
|
const background = document.querySelector(".bg-img") as HTMLImageElement;
|
||||||
|
|
||||||
|
img.onload = function () {
|
||||||
|
if (background) {
|
||||||
|
background.src = img.src;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onerror = function () {
|
||||||
|
img.src = `/images/map_screenshots/${mapName}.jpg`;
|
||||||
|
img.onerror = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
img.src = `/images/map_screenshots/${mapName}.webp`;
|
||||||
|
};
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import router from "../router";
|
|
||||||
|
|
||||||
export const GoToMatch = (id) => {
|
|
||||||
router.push({name: 'Match', params: {match_id: id}})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GoToPlayer = (id) => {
|
|
||||||
router.push({name: 'Player', params: {id: id}})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GoToError = (code) => {
|
|
||||||
router.push({name: code})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GoToLink = (link) => {
|
|
||||||
router.replace(link)
|
|
||||||
}
|
|
||||||
17
src/utils/GoTo.ts
Normal file
17
src/utils/GoTo.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import router from "../router";
|
||||||
|
|
||||||
|
export const GoToMatch = (id: string): void => {
|
||||||
|
router.push({ name: "Match", params: { match_id: id } });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GoToPlayer = (id: string): void => {
|
||||||
|
router.push({ name: "Player", params: { id: id } });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GoToError = (code: string): void => {
|
||||||
|
router.push({ name: code });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GoToLink = (link: string): void => {
|
||||||
|
router.replace(link);
|
||||||
|
};
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
24
src/utils/HLTV.ts
Normal file
24
src/utils/HLTV.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export const GetHLTV_1 = (
|
||||||
|
kills = 0,
|
||||||
|
rounds: number,
|
||||||
|
deaths = 0,
|
||||||
|
k2 = 0,
|
||||||
|
k3 = 0,
|
||||||
|
k4 = 0,
|
||||||
|
k5 = 0
|
||||||
|
): string => {
|
||||||
|
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);
|
||||||
|
};
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
export const SaveLastVisitedToLocalStorage = (data) => {
|
|
||||||
let a = JSON.parse(localStorage.getItem('recent-visited')) || [];
|
|
||||||
|
|
||||||
if (a.length === 0) {
|
|
||||||
a.unshift(data);
|
|
||||||
} else if (a.length === 9) {
|
|
||||||
if (a.find(p => p.steamid64 === data.steamid64)) {
|
|
||||||
a.shift()
|
|
||||||
a.splice(a.findIndex(i => i.steamid64 === data.steamid64), 1)
|
|
||||||
a.unshift(data)
|
|
||||||
} else if (!a.find(p => p.steamid64 === data.steamid64)) {
|
|
||||||
a.shift()
|
|
||||||
a.unshift(data)
|
|
||||||
}
|
|
||||||
} else if (a.length > 0 && a.length < 9) {
|
|
||||||
if (a.find(p => p.steamid64 === data.steamid64)) {
|
|
||||||
a.splice(a.findIndex(i => i.steamid64 === data.steamid64), 1)
|
|
||||||
a.unshift(data)
|
|
||||||
} else if (!a.find(p => p.steamid64 === data.steamid64)) {
|
|
||||||
a.unshift(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.setItem('recent-visited', JSON.stringify(a));
|
|
||||||
}
|
|
||||||
50
src/utils/LocalStorage.ts
Normal file
50
src/utils/LocalStorage.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import type { LocalStoragePlayer } from "@/types";
|
||||||
|
|
||||||
|
export const SaveLastVisitedToLocalStorage = (
|
||||||
|
data: LocalStoragePlayer
|
||||||
|
): void => {
|
||||||
|
const localData = localStorage.getItem("recent-visited");
|
||||||
|
let a: Array<LocalStoragePlayer>;
|
||||||
|
|
||||||
|
if (localData !== null) {
|
||||||
|
a = JSON.parse(localData);
|
||||||
|
|
||||||
|
if (a.length === 0) {
|
||||||
|
} else if (a.length === 9) {
|
||||||
|
if (a.find((p: LocalStoragePlayer) => p.steamId64 === data.steamId64)) {
|
||||||
|
a.shift();
|
||||||
|
a.splice(
|
||||||
|
a.findIndex(
|
||||||
|
(p: LocalStoragePlayer) => p.steamId64 === data.steamId64
|
||||||
|
),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
a.unshift(data);
|
||||||
|
} else if (
|
||||||
|
!a.find((p: LocalStoragePlayer) => p.steamId64 === data.steamId64)
|
||||||
|
) {
|
||||||
|
a.shift();
|
||||||
|
a.unshift(data);
|
||||||
|
}
|
||||||
|
} else if (a.length > 0 && a.length < 9) {
|
||||||
|
if (a.find((p: LocalStoragePlayer) => p.steamId64 === data.steamId64)) {
|
||||||
|
a.splice(
|
||||||
|
a.findIndex(
|
||||||
|
(p: LocalStoragePlayer) => p.steamId64 === data.steamId64
|
||||||
|
),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
a.unshift(data);
|
||||||
|
} else if (
|
||||||
|
!a.find((p: LocalStoragePlayer) => p.steamId64 === data.steamId64)
|
||||||
|
) {
|
||||||
|
a.unshift(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
a = [];
|
||||||
|
a.unshift(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem("recent-visited", JSON.stringify(a));
|
||||||
|
};
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
import {GoToError} from "@/utils/GoTo";
|
|
||||||
|
|
||||||
export const errorHandling = (code) => {
|
|
||||||
if (code === 404) {
|
|
||||||
GoToError('404')
|
|
||||||
} else if (code === 500) {
|
|
||||||
GoToError('500')
|
|
||||||
} else if (code === 502) {
|
|
||||||
GoToError('502')
|
|
||||||
} else {
|
|
||||||
GoToError('404')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setTitle = (title) => {
|
|
||||||
document.title = `${title} | csgoWTF`
|
|
||||||
}
|
|
||||||
|
|
||||||
export const closeNav = (navSelector) => {
|
|
||||||
const nav = document.getElementById(navSelector)
|
|
||||||
if (nav)
|
|
||||||
if (nav.classList.contains('show'))
|
|
||||||
nav.classList.remove('show')
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GetWinLoss = (matchResult, teamId) => {
|
|
||||||
if (matchResult === teamId) {
|
|
||||||
return 'win'
|
|
||||||
} else if (matchResult === 0) {
|
|
||||||
return 'draw'
|
|
||||||
} else {
|
|
||||||
return 'loss'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const truncate = (str, len, ending) => {
|
|
||||||
if (len == null)
|
|
||||||
len = 100
|
|
||||||
|
|
||||||
if (ending == null)
|
|
||||||
ending = '..'
|
|
||||||
|
|
||||||
if (str.length > len)
|
|
||||||
return str.substring(0, len - ending.length) + ending
|
|
||||||
else
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
export const checkStatEmpty = (stat) => {
|
|
||||||
if (stat)
|
|
||||||
return stat
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FixMapName = (map) => {
|
|
||||||
return map.split('_')[1].replace(/^\w/, c => c.toUpperCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getPlayerArr = (stats, team, color) => {
|
|
||||||
let arr = []
|
|
||||||
for (let i = (team - 1) * 5; i < team * 5; i++) {
|
|
||||||
arr.push({
|
|
||||||
value: truncate(stats[i].player.name, 12),
|
|
||||||
textStyle: {
|
|
||||||
color: color ? getComputedStyle(document.documentElement).getPropertyValue(`--csgo-${stats[i].color}`) : 'white'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
arr.reverse()
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
|
|
||||||
export const constructAvatarUrl = (hash, size) => {
|
|
||||||
const base = 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars'
|
|
||||||
const imgSize = size ? `_${size}` : ''
|
|
||||||
|
|
||||||
if (hash) {
|
|
||||||
const hashDir = hash.substring(0, 2)
|
|
||||||
|
|
||||||
return `${base}/${hashDir}/${hash}${imgSize}.jpg`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sortObjectValue = (obj, direction = 'asc') => {
|
|
||||||
const sortable = []
|
|
||||||
for (let key in obj) {
|
|
||||||
sortable.push([key, obj[key]])
|
|
||||||
}
|
|
||||||
|
|
||||||
if (direction === 'asc') {
|
|
||||||
sortable.sort((a, b) => {
|
|
||||||
return a[1] - b[1]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (direction === 'desc') {
|
|
||||||
sortable.sort((a, b) => {
|
|
||||||
return b[1] - a[1]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortable
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CreatePlayersArray = (stats) => {
|
|
||||||
let arr = []
|
|
||||||
for (let i in stats) {
|
|
||||||
arr.push({team_id: stats[i].team_id, player: stats[i].player})
|
|
||||||
}
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
|
|
||||||
export const scrollToPos = (pos = 0) => {
|
|
||||||
window.scrollTo({
|
|
||||||
top: pos,
|
|
||||||
left: 0,
|
|
||||||
behavior: 'smooth'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const StripControlCodes = (str = '') => {
|
|
||||||
const regexpControl = /\p{C}/gu;
|
|
||||||
return str.replace(regexpControl, '')
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ProcessName = (str = '') => {
|
|
||||||
return StripControlCodes(str).trim()
|
|
||||||
}
|
|
||||||
190
src/utils/Utils.ts
Normal file
190
src/utils/Utils.ts
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
import { GoToError } from "@/utils/GoTo";
|
||||||
|
import type { MatchStats, Player } from "@/types";
|
||||||
|
import { decodeMatchShareCode } from "csgo-sharecode";
|
||||||
|
|
||||||
|
enum ErrorPage {
|
||||||
|
INTERNAL_SERVER_ERROR = 500,
|
||||||
|
BAD_GATEWAY = 502,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const errorHandling = (code: number): void => {
|
||||||
|
if (code === ErrorPage.INTERNAL_SERVER_ERROR) {
|
||||||
|
GoToError("500");
|
||||||
|
} else if (code === ErrorPage.BAD_GATEWAY) {
|
||||||
|
GoToError("502");
|
||||||
|
} else {
|
||||||
|
GoToError("404");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setTitle = (title: string): void => {
|
||||||
|
document.title = `${title} | csgoWTF`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const closeNav = (navSelector: string): void => {
|
||||||
|
const nav = document.getElementById(navSelector);
|
||||||
|
if (nav) if (nav.classList.contains("show")) nav.classList.remove("show");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const closeNavEventListener = (navSelector: string): void => {
|
||||||
|
document.addEventListener("click", (e: MouseEvent) => {
|
||||||
|
const divElem = e.target as HTMLDivElement;
|
||||||
|
const mainNav = divElem.querySelector(
|
||||||
|
`#${navSelector}`
|
||||||
|
) as HTMLDivElement | null;
|
||||||
|
if (mainNav === null) {
|
||||||
|
closeNav(navSelector);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
enum WLD {
|
||||||
|
WIN = "win",
|
||||||
|
LOSS = "loss",
|
||||||
|
DRAW = "draw",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GetWinLoss = (matchResult: number, teamId: number): WLD => {
|
||||||
|
if (matchResult === teamId) {
|
||||||
|
return WLD.WIN;
|
||||||
|
} else if (matchResult === 0) {
|
||||||
|
return WLD.DRAW;
|
||||||
|
} else {
|
||||||
|
return WLD.LOSS;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const truncate = (str: string, len: number, ending = ".."): string => {
|
||||||
|
if (len == null) len = 100;
|
||||||
|
|
||||||
|
if (str.length > len) return str.substring(0, len - ending.length) + ending;
|
||||||
|
else return str;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const checkStatEmpty = (stat: never): never | number => {
|
||||||
|
if (stat) return stat;
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FixMapName = (map: string): string => {
|
||||||
|
return map.split("_")[1].replace(/^\w/, (c) => c.toUpperCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
type PlayerArrayType = {
|
||||||
|
value: string;
|
||||||
|
textStyle: PlayerArrayTextStyle;
|
||||||
|
};
|
||||||
|
type PlayerArrayTextStyle = {
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
export const getPlayerArr = (
|
||||||
|
stats: MatchStats[],
|
||||||
|
team: number,
|
||||||
|
color = false
|
||||||
|
): PlayerArrayType[] => {
|
||||||
|
const arr: PlayerArrayType[] = [];
|
||||||
|
for (let i = (team - 1) * 5; i < team * 5; i++) {
|
||||||
|
arr.push({
|
||||||
|
value: truncate(stats[i].player?.name || "", 12),
|
||||||
|
textStyle: {
|
||||||
|
color: color
|
||||||
|
? getComputedStyle(document.documentElement).getPropertyValue(
|
||||||
|
`--csgo-${stats[i].color}`
|
||||||
|
)
|
||||||
|
: "white",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
arr.reverse();
|
||||||
|
return arr;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const constructAvatarUrl = (hash: string, size: string): string => {
|
||||||
|
let output = "";
|
||||||
|
const base =
|
||||||
|
"https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars";
|
||||||
|
const imgSize = size ? `_${size}` : "";
|
||||||
|
|
||||||
|
if (hash) {
|
||||||
|
const hashDir = hash.substring(0, 2);
|
||||||
|
output = `${base}/${hashDir}/${hash}${imgSize}.jpg`;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sortObjectValue = (
|
||||||
|
obj: Record<string, number>,
|
||||||
|
direction = "asc"
|
||||||
|
): Array<[string, number]> => {
|
||||||
|
const sortable: Array<[string, number]> = [];
|
||||||
|
for (const key in obj) {
|
||||||
|
sortable.push([key, obj[key]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction === "asc") {
|
||||||
|
sortable.sort((a, b) => {
|
||||||
|
return a[1] - b[1];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (direction === "desc") {
|
||||||
|
sortable.sort((a, b) => {
|
||||||
|
return b[1] - a[1];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortable;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CreatePlayersArray = (
|
||||||
|
stats: MatchStats[]
|
||||||
|
): Array<Record<string, number | Player | undefined>> => {
|
||||||
|
const arr: Array<Record<string, number | Player | undefined>> = [];
|
||||||
|
for (let i = 0; i < stats.length; i++) {
|
||||||
|
arr.push({ team_id: stats[i].team_id, player: stats[i].player });
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const scrollToPos = (pos = 0): void => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: pos,
|
||||||
|
left: 0,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StripControlCodes = (str = ""): string => {
|
||||||
|
const regexpControl = /\p{C}/gu;
|
||||||
|
return str.replace(regexpControl, "");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProcessName = (str = ""): string => {
|
||||||
|
return StripControlCodes(str).trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setBgImgDisplay = (value: string, type: any): void => {
|
||||||
|
const bgImg = document.querySelector(".bg-img") as typeof type;
|
||||||
|
bgImg.style.display = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setAppDivBackground = (value: string, type: any): void => {
|
||||||
|
const appDiv = document.getElementById("app") as typeof type;
|
||||||
|
appDiv.style.background = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stringSanitizer = (input: string): string => {
|
||||||
|
return input
|
||||||
|
.replaceAll(/[^a-zA-Z0-9;=-\\\\/]/g, "")
|
||||||
|
.replaceAll(/\s{2,}/g, "")
|
||||||
|
.replaceAll(/\r/g, "")
|
||||||
|
.replaceAll(/\n/g, "");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parseShareCode = (shareCode: string): string => {
|
||||||
|
return decodeMatchShareCode(shareCode).matchId.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sleep = (delay: number) => {
|
||||||
|
const start = new Date().getTime();
|
||||||
|
while (new Date().getTime() < start + delay);
|
||||||
|
};
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
import {
|
|
||||||
FormatDate,
|
|
||||||
FormatDuration,
|
|
||||||
FormatFullDate,
|
|
||||||
FormatFullDuration,
|
|
||||||
FormatVacDate,
|
|
||||||
MatchNotParsedTime,
|
|
||||||
ConvertTickToTime
|
|
||||||
} from "./DateTime";
|
|
||||||
import {GoToLink, GoToMatch, GoToPlayer} from "./GoTo";
|
|
||||||
import {SaveLastVisitedToLocalStorage} from "./LocalStorage";
|
|
||||||
import {GetHLTV_1} from "./HLTV";
|
|
||||||
import {DisplayRank, LoadImage, DisplayWeapon} from "./Display";
|
|
||||||
import {
|
|
||||||
GetMatchDetails,
|
|
||||||
GetMatches,
|
|
||||||
GetPlayerMeta,
|
|
||||||
GetPlayerValue,
|
|
||||||
GetUser,
|
|
||||||
GetWeaponDmg,
|
|
||||||
LoadMoreMatches,
|
|
||||||
LoadMoreMatchesExplore,
|
|
||||||
GetChatHistory,
|
|
||||||
GetChatHistoryTranslated,
|
|
||||||
TrackMe
|
|
||||||
} from "./ApiRequests";
|
|
||||||
import {
|
|
||||||
checkStatEmpty,
|
|
||||||
closeNav,
|
|
||||||
constructAvatarUrl,
|
|
||||||
CreatePlayersArray,
|
|
||||||
FixMapName,
|
|
||||||
getPlayerArr,
|
|
||||||
GetWinLoss,
|
|
||||||
setTitle,
|
|
||||||
sortObjectValue,
|
|
||||||
truncate,
|
|
||||||
scrollToPos,
|
|
||||||
StripControlCodes,
|
|
||||||
ProcessName,
|
|
||||||
errorHandling
|
|
||||||
} from "./Utils";
|
|
||||||
|
|
||||||
export {
|
|
||||||
MatchNotParsedTime,
|
|
||||||
GetChatHistoryTranslated,
|
|
||||||
GetChatHistory,
|
|
||||||
ConvertTickToTime,
|
|
||||||
FormatDate,
|
|
||||||
FormatFullDuration,
|
|
||||||
FormatFullDate,
|
|
||||||
FormatDuration,
|
|
||||||
FormatVacDate,
|
|
||||||
GoToMatch,
|
|
||||||
GoToPlayer,
|
|
||||||
GoToLink,
|
|
||||||
SaveLastVisitedToLocalStorage,
|
|
||||||
GetHLTV_1,
|
|
||||||
DisplayRank,
|
|
||||||
LoadImage,
|
|
||||||
GetUser,
|
|
||||||
TrackMe,
|
|
||||||
GetPlayerValue,
|
|
||||||
DisplayWeapon,
|
|
||||||
LoadMoreMatches,
|
|
||||||
GetPlayerMeta,
|
|
||||||
GetMatchDetails,
|
|
||||||
setTitle,
|
|
||||||
GetWinLoss,
|
|
||||||
truncate,
|
|
||||||
checkStatEmpty,
|
|
||||||
getPlayerArr,
|
|
||||||
constructAvatarUrl,
|
|
||||||
FixMapName,
|
|
||||||
closeNav,
|
|
||||||
sortObjectValue,
|
|
||||||
GetWeaponDmg,
|
|
||||||
CreatePlayersArray,
|
|
||||||
GetMatches,
|
|
||||||
LoadMoreMatchesExplore,
|
|
||||||
scrollToPos,
|
|
||||||
StripControlCodes,
|
|
||||||
ProcessName,
|
|
||||||
errorHandling
|
|
||||||
}
|
|
||||||
7
src/utils/index.ts
Normal file
7
src/utils/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export * from "./ApiRequests";
|
||||||
|
export * from "./DateTime";
|
||||||
|
export * from "./Display";
|
||||||
|
export * from "./GoTo";
|
||||||
|
export * from "./HLTV";
|
||||||
|
export * from "./LocalStorage";
|
||||||
|
export * from "./Utils";
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="wrapper">
|
|
||||||
<div class="container-lg text-center">
|
|
||||||
<h3>Recent matches</h3>
|
|
||||||
<div v-if="data.matches">
|
|
||||||
<MatchesTable :key="data.matches" :explore="true" :matches="data.matches" />
|
|
||||||
|
|
||||||
<div class="load-more text-center">
|
|
||||||
<button :key="scrollToPos(store.state.scroll_state)" class="btn border-2 btn-outline-info"
|
|
||||||
@click="setMoreMatches">Load More
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
<hr>
|
|
||||||
<h6>There seems to be a problem loading the content</h6>
|
|
||||||
<h6>Please try again later</h6>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {onBeforeUnmount, onMounted, reactive} from "vue";
|
|
||||||
import {GetMatches, LoadImage, LoadMoreMatchesExplore, MatchNotParsedTime, scrollToPos} from "@/utils";
|
|
||||||
import MatchesTable from "@/components/MatchesTable";
|
|
||||||
import {useStore} from "vuex";
|
|
||||||
import router from "@/router";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Explore',
|
|
||||||
components: {MatchesTable},
|
|
||||||
setup() {
|
|
||||||
document.title = "Matches | csgoWTF"
|
|
||||||
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const data = reactive({
|
|
||||||
matches: []
|
|
||||||
})
|
|
||||||
|
|
||||||
const setMoreMatches = async () => {
|
|
||||||
const res = await LoadMoreMatchesExplore(store, data.matches[data.matches.length - 1].date)
|
|
||||||
|
|
||||||
if (res !== null)
|
|
||||||
res.forEach(e => data.matches.push(e))
|
|
||||||
|
|
||||||
scrollToPos(window.scrollY)
|
|
||||||
|
|
||||||
// console.log(data.matches)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
data.matches = await GetMatches(store)
|
|
||||||
|
|
||||||
if (data.matches !== null) {
|
|
||||||
if (data.matches[0].map) {
|
|
||||||
await LoadImage(data.matches[0].map)
|
|
||||||
} else if (!data.matches[0].map && MatchNotParsedTime(data.matches[0].date) && data.matches[1].map) {
|
|
||||||
await LoadImage(data.matches[1].map)
|
|
||||||
} else {
|
|
||||||
await LoadImage('random')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
document.querySelector('.bg-img').style.display = 'none'
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollToPos(store.state.scroll_state)
|
|
||||||
|
|
||||||
// if (data.matches) {
|
|
||||||
// console.log(data.matches)
|
|
||||||
// }
|
|
||||||
|
|
||||||
document.getElementById('app').style.background = 'rgba(0, 0, 0, .7)'
|
|
||||||
document.querySelector('.bg-img').style.display = 'initial'
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
store.commit('changeScrollState', window.scrollY)
|
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
|
||||||
if (!to.fullPath.match('/match/') && !from.fullPath.match('/match/')) {
|
|
||||||
store.commit('changeScrollState', 0)
|
|
||||||
}
|
|
||||||
next()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return {data, setMoreMatches, store, scrollToPos}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.container-lg {
|
|
||||||
padding: 2rem;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-more {
|
|
||||||
padding: 1rem 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
.container-lg {
|
|
||||||
padding: 2rem 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
126
src/views/ExploreView.vue
Normal file
126
src/views/ExploreView.vue
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
<template>
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="container-lg text-center">
|
||||||
|
<h3>Recent matches</h3>
|
||||||
|
<div v-if="data.matches">
|
||||||
|
<MatchesTable
|
||||||
|
:key="data.matches"
|
||||||
|
:explore="true"
|
||||||
|
:matches="data.matches"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="load-more text-center">
|
||||||
|
<button
|
||||||
|
:key="scrollToPos(scrollStateStore.scrollState)"
|
||||||
|
class="btn border-2 btn-outline-info"
|
||||||
|
@click="setMoreMatches"
|
||||||
|
>
|
||||||
|
Load More
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<hr />
|
||||||
|
<h6>There seems to be a problem loading the content</h6>
|
||||||
|
<h6>Please try again later</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onBeforeUnmount, onMounted, reactive } from "vue";
|
||||||
|
import {
|
||||||
|
scrollToPos,
|
||||||
|
setAppDivBackground,
|
||||||
|
setBgImgDisplay,
|
||||||
|
MatchNotParsedTime,
|
||||||
|
LoadImage,
|
||||||
|
GetMatches, LoadMoreMatchesExplore
|
||||||
|
} from "@/utils";
|
||||||
|
import router from "@/router";
|
||||||
|
import MatchesTable from "@/components/MatchesTable.vue";
|
||||||
|
import type { Match } from "@/types";
|
||||||
|
import { useScrollStateStore } from "@/stores/scrollState";
|
||||||
|
import { useMatchDetailsStore } from "@/stores/matchDetails";
|
||||||
|
import {useInfoStateStore} from "@/stores/infoState";
|
||||||
|
|
||||||
|
document.title = "Matches | csgoWTF";
|
||||||
|
|
||||||
|
const scrollStateStore = useScrollStateStore();
|
||||||
|
const matchDetailsStore = useMatchDetailsStore();
|
||||||
|
const infoStateStore = useInfoStateStore()
|
||||||
|
|
||||||
|
const data = reactive({
|
||||||
|
matches: ([] as Match[]) || null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const setMoreMatches = async () => {
|
||||||
|
const [res, info] = await LoadMoreMatchesExplore(
|
||||||
|
data.matches[data.matches.length - 1].date
|
||||||
|
);
|
||||||
|
|
||||||
|
if (info.message !== "") infoStateStore.addInfo(info)
|
||||||
|
if (res !== null) res.forEach((e) => data.matches.push(e));
|
||||||
|
|
||||||
|
scrollToPos(window.scrollY);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const [res, info] = await GetMatches();
|
||||||
|
if (info.message !== "") infoStateStore.addInfo(info)
|
||||||
|
if (res !== null) data.matches = res;
|
||||||
|
|
||||||
|
if (data.matches !== null) {
|
||||||
|
if (data.matches[0].map) {
|
||||||
|
await LoadImage(data.matches[0].map);
|
||||||
|
} else if (
|
||||||
|
!data.matches[0].map &&
|
||||||
|
MatchNotParsedTime(data.matches[0].date) &&
|
||||||
|
data.matches[1].map
|
||||||
|
) {
|
||||||
|
await LoadImage(data.matches[1].map);
|
||||||
|
} else {
|
||||||
|
await LoadImage("random");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setBgImgDisplay("none", HTMLImageElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToPos(scrollStateStore.scrollState);
|
||||||
|
setAppDivBackground("rgba(0, 0, 0, 0.7)", HTMLDivElement);
|
||||||
|
setBgImgDisplay("initial", HTMLImageElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
scrollStateStore.scrollState = window.scrollY;
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
if (!to.fullPath.match("/match/") && !from.fullPath.match("/match/")) {
|
||||||
|
scrollStateStore.scrollState = 0;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container-lg {
|
||||||
|
padding: 2rem;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-more {
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.container-lg {
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,278 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="main-content content text-center">
|
|
||||||
<div class="head pt-4 pb-4">
|
|
||||||
<img alt="logo"
|
|
||||||
class="logo mt-lg-5 mt-3 mb-3"
|
|
||||||
src="/images/logo.svg">
|
|
||||||
<h3 class="mb-lg-4">Open source CSGO data platform</h3>
|
|
||||||
</div>
|
|
||||||
<div v-if="recentVisited !== null" class="recent-search mt-5 mb-5 row gap-2 justify-content-center">
|
|
||||||
<div v-for="(player, id) in recentVisited" :key="player.steamid64" class="player-card" tabindex="0"
|
|
||||||
@keyup.enter="GoToPlayer(player.vanity_url || player.steamid64)">
|
|
||||||
<div class="p-2" @click="GoToPlayer(player.vanity_url || player.steamid64)">
|
|
||||||
<div class="col-md-4 m-auto">
|
|
||||||
<img :alt="player.name" :src="player.avatar">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-8 m-auto">
|
|
||||||
<p>{{ player.name }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<i class="delete fa fa-times" tabindex="0" @click="removeRecentVisited(id)"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr v-if="recentVisited !== null" class="m-auto text-muted">
|
|
||||||
<div class="body container m-auto row mt-5 mb-5 justify-content-center">
|
|
||||||
<table class="table table-borderless">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<i class="fa fa-code-fork"/>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<i class="fa fa-liberapay"/>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<i class="fa fa-pie-chart"/>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr class="align-middle">
|
|
||||||
<td>
|
|
||||||
<h4 class="fw-light">Open Source</h4>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://liberapay.com/CSGOWTF/donate" target="_blank">
|
|
||||||
<img alt="Donate using Liberapay"
|
|
||||||
src="https://liberapay.com/assets/widgets/donate.svg"
|
|
||||||
style="height: 35px">
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<h4 class="fw-light">In-Depth Data</h4>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<p class="fw-light">Everything is open source and under GPL licence. Contributions welcome.</p>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<p class="fw-light">We develop this site in our spare time. If you want to support us, donations are
|
|
||||||
appreciated!</p>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<p class="fw-light">Matches with parsed replay provide additional match data.</p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td/>
|
|
||||||
<td>
|
|
||||||
<img alt="liberapay patrons" src="https://img.shields.io/liberapay/patrons/CSGOWTF.svg"
|
|
||||||
style="height: 25px"/>
|
|
||||||
</td>
|
|
||||||
<td/>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {GoToPlayer, SaveLastVisitedToLocalStorage, setTitle} from "@/utils";
|
|
||||||
import {onBeforeMount, ref} from "vue";
|
|
||||||
import {useStore} from "vuex";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Home',
|
|
||||||
setup() {
|
|
||||||
setTitle('Home')
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const recentVisited = ref([])
|
|
||||||
|
|
||||||
const loadRecentVisited = () => {
|
|
||||||
recentVisited.value = JSON.parse(localStorage.getItem('recent-visited'))
|
|
||||||
|
|
||||||
if (recentVisited.value !== null) {
|
|
||||||
if (window.innerWidth < 768) {
|
|
||||||
recentVisited.value = recentVisited.value.filter(i => recentVisited.value.indexOf(i) < 6)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeRecentVisited = (key) => {
|
|
||||||
if (recentVisited.value !== null) {
|
|
||||||
recentVisited.value.splice(key, 1)
|
|
||||||
recentVisited.value.reverse()
|
|
||||||
|
|
||||||
localStorage.clear()
|
|
||||||
|
|
||||||
if (recentVisited.value !== []) {
|
|
||||||
recentVisited.value.map(p => {
|
|
||||||
SaveLastVisitedToLocalStorage(p)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadRecentVisited()
|
|
||||||
}
|
|
||||||
|
|
||||||
onBeforeMount(() => {
|
|
||||||
loadRecentVisited()
|
|
||||||
store.commit('resetPlayerDetails')
|
|
||||||
|
|
||||||
document.getElementById('app').style.background = 'none'
|
|
||||||
document.querySelector('.bg-img').style.display = 'none'
|
|
||||||
})
|
|
||||||
|
|
||||||
return {recentVisited, GoToPlayer, removeRecentVisited}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
table {
|
|
||||||
td {
|
|
||||||
p {
|
|
||||||
max-width: 40ch;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.fa {
|
|
||||||
font-size: 5rem;
|
|
||||||
padding-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-content {
|
|
||||||
.head {
|
|
||||||
// display jpg
|
|
||||||
background-image: url("/images/map_screenshots/default.jpg");
|
|
||||||
}
|
|
||||||
|
|
||||||
.head {
|
|
||||||
// display webp if possible
|
|
||||||
background-image: url("/images/map_screenshots/default.webp");
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-up {
|
|
||||||
font-family: "OpenSans", sans-serif;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.recent-search {
|
|
||||||
max-width: 1100px;
|
|
||||||
margin: 0 auto;
|
|
||||||
|
|
||||||
.player-card {
|
|
||||||
width: 180px;
|
|
||||||
height: 75px;
|
|
||||||
background: var(--bs-blue);
|
|
||||||
border-radius: 15% 5%;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.delete {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--bs-primary);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
background: var(--bs-warning) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover > .delete {
|
|
||||||
display: initial;
|
|
||||||
position: absolute;
|
|
||||||
font-size: 1rem;
|
|
||||||
top: 5px;
|
|
||||||
right: 5px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: maroon;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: .9rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 576px) {
|
|
||||||
.logo {
|
|
||||||
width: 200px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
.head {
|
|
||||||
.logo {
|
|
||||||
width: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 2rem;
|
|
||||||
padding: 0 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.recent-search {
|
|
||||||
.player-card {
|
|
||||||
height: 60px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete {
|
|
||||||
display: initial;
|
|
||||||
position: absolute;
|
|
||||||
font-size: 1rem;
|
|
||||||
top: 5px;
|
|
||||||
right: 5px;
|
|
||||||
color: maroon;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.body {
|
|
||||||
p {
|
|
||||||
font-size: .9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fas {
|
|
||||||
font-size: 3rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
307
src/views/HomeView.vue
Normal file
307
src/views/HomeView.vue
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
<template>
|
||||||
|
<div class="main-content content text-center">
|
||||||
|
<div class="head pt-4 pb-4">
|
||||||
|
<img alt="logo" class="logo mt-lg-5 mt-3 mb-3" src="/images/logo.svg" />
|
||||||
|
<h3 class="mb-lg-4">Open source CSGO data platform</h3>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="recentVisited !== null"
|
||||||
|
class="recent-search mt-5 mb-5 row gap-2 justify-content-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(player, id) in recentVisited"
|
||||||
|
:key="player.steamId64"
|
||||||
|
class="player-card"
|
||||||
|
tabindex="0"
|
||||||
|
@keyup.enter="GoToPlayer(player.vanityUrl || player.steamId64)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="p-2"
|
||||||
|
@click="GoToPlayer(player.vanityUrl || player.steamId64)"
|
||||||
|
>
|
||||||
|
<div class="col-md-4 m-auto">
|
||||||
|
<img :alt="player.name" :src="player.avatar" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8 m-auto">
|
||||||
|
<p>{{ player.name }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<i
|
||||||
|
class="delete fa fa-times"
|
||||||
|
tabindex="0"
|
||||||
|
@click="removeRecentVisited(id)"
|
||||||
|
></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr v-if="recentVisited !== null" class="m-auto text-muted" />
|
||||||
|
<div class="body container m-auto row mt-5 mb-5 justify-content-center">
|
||||||
|
<table class="table table-borderless">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<i class="fa fa-code-fork" />
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<i class="fa fa-liberapay" />
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<i class="fa fa-pie-chart" />
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="align-middle">
|
||||||
|
<td>
|
||||||
|
<h4 class="fw-light">Open Source</h4>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://liberapay.com/CSGOWTF/donate" target="_blank">
|
||||||
|
<img
|
||||||
|
alt="Donate using Liberapay"
|
||||||
|
src="https://liberapay.com/assets/widgets/donate.svg"
|
||||||
|
style="height: 35px"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<h4 class="fw-light">In-Depth Data</h4>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<p class="fw-light">
|
||||||
|
Everything is open source and under GPL licence. Contributions
|
||||||
|
welcome.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p class="fw-light">
|
||||||
|
We develop this site in our spare time. If you want to support
|
||||||
|
us, donations are appreciated!
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p class="fw-light">
|
||||||
|
Matches with parsed replay provide additional match data.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td />
|
||||||
|
<td>
|
||||||
|
<img
|
||||||
|
alt="liberapay patrons"
|
||||||
|
src="https://img.shields.io/liberapay/patrons/CSGOWTF.svg"
|
||||||
|
style="height: 25px"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td />
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
GoToPlayer,
|
||||||
|
SaveLastVisitedToLocalStorage,
|
||||||
|
setAppDivBackground,
|
||||||
|
setBgImgDisplay,
|
||||||
|
setTitle,
|
||||||
|
} from "@/utils";
|
||||||
|
import { onBeforeMount, ref } from "vue";
|
||||||
|
import { usePlayerDetailsStore } from "@/stores/playerDetails";
|
||||||
|
|
||||||
|
setTitle("Home");
|
||||||
|
|
||||||
|
const playerDetailsStore = usePlayerDetailsStore();
|
||||||
|
const recentVisited = ref([]);
|
||||||
|
|
||||||
|
const loadRecentVisited = () => {
|
||||||
|
const localString = localStorage.getItem("recent-visited");
|
||||||
|
if (typeof localString === "string")
|
||||||
|
recentVisited.value = JSON.parse(localString);
|
||||||
|
|
||||||
|
if (recentVisited.value !== []) {
|
||||||
|
if (window.innerWidth < 768) {
|
||||||
|
recentVisited.value = recentVisited.value.filter(
|
||||||
|
(i) => recentVisited.value.indexOf(i) < 6
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeRecentVisited = (key: number) => {
|
||||||
|
if (recentVisited.value !== []) {
|
||||||
|
recentVisited.value.splice(key, 1);
|
||||||
|
recentVisited.value.reverse();
|
||||||
|
|
||||||
|
localStorage.clear();
|
||||||
|
|
||||||
|
if (recentVisited.value !== []) {
|
||||||
|
recentVisited.value.map((p) => {
|
||||||
|
SaveLastVisitedToLocalStorage(p);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRecentVisited();
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
loadRecentVisited();
|
||||||
|
playerDetailsStore.$reset();
|
||||||
|
|
||||||
|
setAppDivBackground("none", HTMLDivElement);
|
||||||
|
setBgImgDisplay("none", HTMLImageElement);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
table {
|
||||||
|
td {
|
||||||
|
p {
|
||||||
|
max-width: 40ch;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
font-size: 5rem;
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
.head {
|
||||||
|
// display jpg
|
||||||
|
background-image: url("/images/map_screenshots/default.jpg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.head {
|
||||||
|
// display webp if possible
|
||||||
|
background-image: url("/images/map_screenshots/default.webp");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-up {
|
||||||
|
font-family: "OpenSans", sans-serif;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-search {
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.player-card {
|
||||||
|
width: 180px;
|
||||||
|
height: 75px;
|
||||||
|
background: var(--bs-blue);
|
||||||
|
border-radius: 15% 5%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--bs-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
background: var(--bs-warning) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover > .delete {
|
||||||
|
display: initial;
|
||||||
|
position: absolute;
|
||||||
|
font-size: 1rem;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: maroon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 576px) {
|
||||||
|
.logo {
|
||||||
|
width: 200px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.head {
|
||||||
|
.logo {
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 2rem;
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.recent-search {
|
||||||
|
.player-card {
|
||||||
|
height: 60px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
display: initial;
|
||||||
|
position: absolute;
|
||||||
|
font-size: 1rem;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
color: maroon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
p {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fas {
|
||||||
|
font-size: 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,582 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="overlay" :style="{minHeight: pHeight + 'px'}">
|
|
||||||
<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>
|
|
||||||
import {onBeforeMount, onBeforeUnmount, onMounted, reactive, ref, watch} from "vue";
|
|
||||||
import {
|
|
||||||
closeNav,
|
|
||||||
CreatePlayersArray,
|
|
||||||
DisplayRank,
|
|
||||||
errorHandling,
|
|
||||||
FixMapName,
|
|
||||||
FormatDuration,
|
|
||||||
FormatFullDate,
|
|
||||||
GetMatchDetails,
|
|
||||||
GoToLink,
|
|
||||||
LoadImage,
|
|
||||||
ProcessName
|
|
||||||
} from "@/utils";
|
|
||||||
import {useStore} from "vuex";
|
|
||||||
import {useRoute} from 'vue-router'
|
|
||||||
import {DateTime} from "luxon/build/es6/luxon";
|
|
||||||
import {FOOTER_HEIGHT, NAV_HEIGHT} from "@/constants";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Match',
|
|
||||||
props: ['match_id'],
|
|
||||||
setup(props) {
|
|
||||||
const store = useStore()
|
|
||||||
const route = useRoute()
|
|
||||||
const pHeight = ref(0)
|
|
||||||
|
|
||||||
const matchIdPattern = /^\d{19}$/
|
|
||||||
|
|
||||||
// Refs
|
|
||||||
const data = reactive({
|
|
||||||
player_id: '',
|
|
||||||
matchDetails: {},
|
|
||||||
stats: [],
|
|
||||||
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 = await GetMatchDetails(store, props.match_id)
|
|
||||||
|
|
||||||
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`
|
|
||||||
|
|
||||||
store.commit({
|
|
||||||
type: 'changeMatchDetails',
|
|
||||||
data: res
|
|
||||||
})
|
|
||||||
|
|
||||||
checkRoute()
|
|
||||||
data.matchDetails = store.state.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 + b)
|
|
||||||
})) / pCount
|
|
||||||
|
|
||||||
pCount = 1
|
|
||||||
data.team2Avg = Math.floor(getTeamAvgRank(2).reduce((a, b) => {
|
|
||||||
if (a !== 0 && b !== 0)
|
|
||||||
pCount++
|
|
||||||
return (a + b)
|
|
||||||
})) / pCount
|
|
||||||
|
|
||||||
LoadImage(data.matchDetails.map ? data.matchDetails.map : 'random')
|
|
||||||
|
|
||||||
store.commit({
|
|
||||||
type: 'changePlayersArr',
|
|
||||||
data: CreatePlayersArray(data.stats)
|
|
||||||
})
|
|
||||||
|
|
||||||
// console.log(data.matchDetails)
|
|
||||||
} else {
|
|
||||||
document.querySelector('.bg-img').style.display = 'none'
|
|
||||||
}
|
|
||||||
} 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) => {
|
|
||||||
let arr = []
|
|
||||||
for (let i = (team - 1) * 5; i < team * 5; i++) {
|
|
||||||
arr.push(data.matchDetails.stats[i].rank?.old !== undefined ? data.matchDetails.stats[i].rank?.old : 0)
|
|
||||||
}
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDownloadMenu = () => {
|
|
||||||
const downloadGroup = document.getElementById('downloadGroup')
|
|
||||||
const menuBtn = document.getElementById('downloadMenuBtn')
|
|
||||||
let opacity = window.getComputedStyle(menuBtn).getPropertyValue('opacity')
|
|
||||||
|
|
||||||
function show() {
|
|
||||||
if (opacity < 1) {
|
|
||||||
opacity = opacity + 0.1
|
|
||||||
downloadGroup.style.opacity = opacity
|
|
||||||
} else {
|
|
||||||
clearInterval(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hide() {
|
|
||||||
if (opacity > 0) {
|
|
||||||
opacity = opacity - 0.1
|
|
||||||
menuBtn.style.opacity = opacity
|
|
||||||
} else {
|
|
||||||
menuBtn.style.display = 'none'
|
|
||||||
downloadGroup.style.opacity = 0
|
|
||||||
downloadGroup.style.display = 'block'
|
|
||||||
setInterval(show, 35)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setInterval(hide, 35)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watchers
|
|
||||||
watch(() => props.match_id, GetMatch)
|
|
||||||
|
|
||||||
// Run on create
|
|
||||||
onBeforeMount(() => {
|
|
||||||
GetMatch()
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
store.commit('resetMatchDetails')
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const headHeight = 230
|
|
||||||
const navHeight = 42
|
|
||||||
|
|
||||||
const height = window.innerHeight - NAV_HEIGHT - FOOTER_HEIGHT - headHeight - navHeight
|
|
||||||
const scoreWrapper = document.getElementById('scoreWrapper')
|
|
||||||
scoreWrapper.style.minHeight = height + 'px'
|
|
||||||
|
|
||||||
document.getElementById('app').style.background = '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%)'
|
|
||||||
document.querySelector('.bg-img').style.display = 'initial'
|
|
||||||
})
|
|
||||||
|
|
||||||
window.onresize = () => {
|
|
||||||
pHeight.value = getWindowHeight()
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('click', () => {
|
|
||||||
closeNav('matchNav')
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
data, DisplayRank, FormatFullDate, FormatDuration, FixMapName, route, pHeight, handleDownloadMenu, getTeamAvgRank
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</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, .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 .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: .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, .7) 100%
|
|
||||||
);
|
|
||||||
border-top: 1px solid rgba(255, 255, 255, .2);
|
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, .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>
|
|
||||||
760
src/views/MatchView.vue
Normal file
760
src/views/MatchView.vue
Normal file
@@ -0,0 +1,760 @@
|
|||||||
|
<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>
|
||||||
@@ -1,543 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="wrapper" :style="{minHeight: pHeight + 'px'}">
|
|
||||||
<div class="container-lg">
|
|
||||||
<div v-if="store.state.playerDetails.name">
|
|
||||||
<div class="card mb-3 bg-transparent border-0">
|
|
||||||
<div class="row g-0">
|
|
||||||
<div class="img-container col-md-2 pt-3">
|
|
||||||
<img
|
|
||||||
:class="data.tracked ? 'tracked' : ''"
|
|
||||||
:src="constructAvatarUrl(store.state.playerDetails.avatar, 'full')"
|
|
||||||
:title="data.tracked ? 'Tracked' : ''"
|
|
||||||
alt="Player avatar"
|
|
||||||
class="img-fluid avatar">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-8 d-flex">
|
|
||||||
<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">{{
|
|
||||||
store.state.playerDetails.name
|
|
||||||
}}
|
|
||||||
<i class="fa fa-steam"></i>
|
|
||||||
</a></h3>
|
|
||||||
<table class="table table-borderless text-center">
|
|
||||||
<tr>
|
|
||||||
<th class="wlt-win text-uppercase text-muted">Wins</th>
|
|
||||||
<th class="wlt-loss text-uppercase text-muted">Losses</th>
|
|
||||||
<th class="wlt-tie text-uppercase text-muted">Ties</th>
|
|
||||||
<th class="wlt-win-rate text-uppercase text-muted">Win-Rate</th>
|
|
||||||
<th class="wlt-tie-rate text-uppercase text-muted">Tie-Rate</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="wlt-win">{{ data.match_stats.win }}</td>
|
|
||||||
<td class="wlt-loss">{{ data.match_stats.loss }}</td>
|
|
||||||
<td class="wlt-tie">{{ data.match_stats.tie }}</td>
|
|
||||||
<td class="wlt-win-rate">{{
|
|
||||||
data.match_stats.win > 0 ? (data.match_stats.win / data.match_stats.total * 100).toFixed(0) : 0
|
|
||||||
}}%
|
|
||||||
</td>
|
|
||||||
<td class="wlt-tie-rate">{{
|
|
||||||
data.match_stats.tie > 0 ? (data.match_stats.tie / data.match_stats.total * 100).toFixed(0) : 0
|
|
||||||
}}%
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<div class="badges">
|
|
||||||
<img v-if="store.state.playerDetails.vac"
|
|
||||||
:title="'VAC-Ban: ' + FormatVacDate(store.state.playerDetails.vac_date, store.state.matchDetails.date)"
|
|
||||||
alt="Vac banned"
|
|
||||||
src="/images/icons/vac_banned.svg">
|
|
||||||
<img v-if="store.state.playerDetails.game_ban"
|
|
||||||
:title="'Game-Ban: ' + FormatVacDate(store.state.playerDetails.game_ban_date, store.state.matchDetails.date)"
|
|
||||||
alt="Game banned"
|
|
||||||
src="/images/icons/game_banned.svg">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!data.tracked" class="dropdown trackme-btn">
|
|
||||||
<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)"
|
|
||||||
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="store.state.playerDetails.matches ? 'ShareCode (optional)' : 'ShareCode (required)'"
|
|
||||||
:required="!store.state.playerDetails.matches"
|
|
||||||
type="text"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-outline mb-4">
|
|
||||||
<small>
|
|
||||||
<a href="https://help.steampowered.com/en/wizard/HelpWithGameIssue/?appid=730&issueid=128"
|
|
||||||
target="_blank">
|
|
||||||
Here you can find your AuthCode and ShareCode
|
|
||||||
</a>
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Submit button -->
|
|
||||||
<button class="btn btn-outline-warning border-2" type="submit"
|
|
||||||
@click.prevent="TrackPlayer">
|
|
||||||
TrackMe
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="data.tracked" class="refresh-btn" title="Refresh Match-List" @click="RefreshData">
|
|
||||||
<i class="fa fa-refresh fa-2x"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="match-container d-flex">
|
|
||||||
<div class="matches">
|
|
||||||
|
|
||||||
<MatchesTable v-if="store.state.playerDetails.matches" :matches="store.state.playerDetails.matches" color-front />
|
|
||||||
<h5 v-else>Track yourself to see your matches</h5>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="store.state.playerDetails.matches" class="side-info-container">
|
|
||||||
|
|
||||||
<PlayerSideInfo :player_meta="data.playerMeta"/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="load-more col-lg-9 col-md-12 text-center">
|
|
||||||
<button v-if="data.match_stats.total !== data.matches.length" :key="scrollToPos(store.state.scroll_state)"
|
|
||||||
class="btn border-2 btn-outline-info" @click="setMoreMatches">Load More
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="text-center pt-5">
|
|
||||||
<h3>Player-Page</h3>
|
|
||||||
<hr>
|
|
||||||
<h6>There seems to be a problem loading the player</h6>
|
|
||||||
<h6>Please try again later</h6>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {onBeforeMount, onBeforeUnmount, onMounted, reactive, ref, watch} from "vue";
|
|
||||||
import {useStore} from "vuex";
|
|
||||||
import {
|
|
||||||
constructAvatarUrl,
|
|
||||||
DisplayRank,
|
|
||||||
FixMapName,
|
|
||||||
FormatVacDate,
|
|
||||||
GetPlayerMeta,
|
|
||||||
GetUser,
|
|
||||||
GetWinLoss,
|
|
||||||
GoToPlayer,
|
|
||||||
LoadImage,
|
|
||||||
LoadMoreMatches,
|
|
||||||
MatchNotParsedTime,
|
|
||||||
ProcessName,
|
|
||||||
SaveLastVisitedToLocalStorage,
|
|
||||||
scrollToPos,
|
|
||||||
setTitle,
|
|
||||||
TrackMe
|
|
||||||
} from "@/utils";
|
|
||||||
import {FOOTER_HEIGHT, NAV_HEIGHT} from "@/constants";
|
|
||||||
import MatchesTable from "@/components/MatchesTable";
|
|
||||||
import router from "@/router";
|
|
||||||
import PlayerSideInfo from "@/components/PlayerSideInfo";
|
|
||||||
import {StatusCodes as STATUS} from "http-status-codes";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Player',
|
|
||||||
components: {PlayerSideInfo, MatchesTable},
|
|
||||||
props: ['id'],
|
|
||||||
setup(props) {
|
|
||||||
// Variables
|
|
||||||
const store = useStore()
|
|
||||||
const pHeight = ref(0)
|
|
||||||
const displayCounter = 3
|
|
||||||
|
|
||||||
const data = reactive({
|
|
||||||
userData: {
|
|
||||||
authcode: '',
|
|
||||||
sharecode: ''
|
|
||||||
},
|
|
||||||
tracked: false,
|
|
||||||
matches: [],
|
|
||||||
match_stats: {
|
|
||||||
loss: 0,
|
|
||||||
win: 0,
|
|
||||||
tie: 0,
|
|
||||||
total: 0
|
|
||||||
},
|
|
||||||
playerMeta: {},
|
|
||||||
})
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
onBeforeMount(() => {
|
|
||||||
if (Object.entries(store.state.playerDetails).length === 0) {
|
|
||||||
GetPlayer()
|
|
||||||
} else {
|
|
||||||
// console.log(store.state.playerDetails)
|
|
||||||
SetPlayerData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const SetPlayerData = async () => {
|
|
||||||
data.tracked = store.state.playerDetails.tracked
|
|
||||||
if (store.state.playerDetails.matches)
|
|
||||||
data.matches = store.state.playerDetails.matches
|
|
||||||
if (store.state.playerDetails.match_stats) {
|
|
||||||
data.match_stats.loss = store.state.playerDetails.match_stats.loss || 0
|
|
||||||
data.match_stats.win = store.state.playerDetails.match_stats.win || 0
|
|
||||||
data.match_stats.tie = store.state.playerDetails.match_stats.tie || 0
|
|
||||||
data.match_stats.total = data.match_stats.loss + data.match_stats.win + data.match_stats.tie
|
|
||||||
}
|
|
||||||
|
|
||||||
store.commit({
|
|
||||||
type: 'changeId64',
|
|
||||||
id: store.state.playerDetails.steamid64
|
|
||||||
})
|
|
||||||
store.commit({
|
|
||||||
type: 'changeVanityUrl',
|
|
||||||
id: store.state.playerDetails.vanity_url || ''
|
|
||||||
})
|
|
||||||
|
|
||||||
if (store.state.playerDetails.matches) {
|
|
||||||
if (data.matches[0].map) {
|
|
||||||
await LoadImage(data.matches[0].map)
|
|
||||||
} else if (!data.matches[0].map && MatchNotParsedTime(data.matches[0].date) && data.matches[1].map) {
|
|
||||||
await LoadImage(data.matches[1].map)
|
|
||||||
} else {
|
|
||||||
await LoadImage('random')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await LoadImage('random')
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelector('.bg-img').style.display = 'initial'
|
|
||||||
document.getElementById('app').style.background = 'rgba(0, 0, 0, .7)'
|
|
||||||
|
|
||||||
let player = {
|
|
||||||
'steamid64': store.state.playerDetails.steamid64,
|
|
||||||
'vanity_url': store.state.playerDetails.vanity_url || '',
|
|
||||||
'name': store.state.playerDetails.name,
|
|
||||||
'avatar': constructAvatarUrl(store.state.playerDetails.avatar, 'medium')
|
|
||||||
}
|
|
||||||
SaveLastVisitedToLocalStorage(player)
|
|
||||||
|
|
||||||
setTitle(store.state.playerDetails.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
const GetPlayer = async (reset = false) => {
|
|
||||||
if (props.id) {
|
|
||||||
const resData = await GetUser(store, props.id)
|
|
||||||
|
|
||||||
if (resData !== null) {
|
|
||||||
if (resData.steamid64 !== store.state.playerDetails.steamid64 || reset) {
|
|
||||||
resData.name = ProcessName(resData.name)
|
|
||||||
|
|
||||||
store.commit('resetPlayerDetails')
|
|
||||||
store.commit({
|
|
||||||
type: 'changePlayerDetails',
|
|
||||||
data: resData
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await SetPlayerData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const setMoreMatches = async () => {
|
|
||||||
const res = await LoadMoreMatches(store, store.state.playerDetails.steamid64, data.matches[data.matches.length - 1].date)
|
|
||||||
|
|
||||||
if (res !== null)
|
|
||||||
await res.matches.forEach(e => data.matches.push(e))
|
|
||||||
|
|
||||||
scrollToPos(window.scrollY)
|
|
||||||
|
|
||||||
// console.log(store.state.playerDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
const RefreshData = async () => {
|
|
||||||
const refreshButton = document.querySelector('.refresh-btn .fa')
|
|
||||||
refreshButton.classList.add('fa-spin')
|
|
||||||
refreshButton.classList.add('fa-fw')
|
|
||||||
refreshButton.classList.remove('fa-refresh')
|
|
||||||
refreshButton.classList.add('fa-spinner')
|
|
||||||
scrollToPos(0)
|
|
||||||
|
|
||||||
await GetPlayer(true).then(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
refreshButton.classList.remove('fa-spin')
|
|
||||||
refreshButton.classList.remove('fa-fw')
|
|
||||||
refreshButton.classList.add('fa-refresh')
|
|
||||||
refreshButton.classList.remove('fa-spinner')
|
|
||||||
}, 2000)
|
|
||||||
})
|
|
||||||
|
|
||||||
data.playerMeta = await GetPlayerMeta(store, props.id, displayCounter)
|
|
||||||
if (data.playerMeta === null)
|
|
||||||
data.playerMeta = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const TrackPlayer = async () => {
|
|
||||||
let message = ''
|
|
||||||
|
|
||||||
if (data.matches.length === 0) {
|
|
||||||
if (data.userData.sharecode === '') {
|
|
||||||
message = 'Sharecode is missing'
|
|
||||||
}
|
|
||||||
if (data.userData.authcode === '') {
|
|
||||||
message = 'Authcode is missing'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (data.userData.authcode === '') {
|
|
||||||
message = 'Authcode is missing'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message !== '') {
|
|
||||||
store.commit({
|
|
||||||
type: 'changeInfoState',
|
|
||||||
data: {
|
|
||||||
statuscode: STATUS.IM_A_TEAPOT,
|
|
||||||
message: message,
|
|
||||||
type: 'error'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
const res = await TrackMe(store, store.state.playerDetails.steamid64, data.userData.authcode, data.userData.sharecode)
|
|
||||||
|
|
||||||
if (res !== null && res === STATUS.ACCEPTED) {
|
|
||||||
location.reload()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => props.id, async () => {
|
|
||||||
await GetPlayer()
|
|
||||||
data.playerMeta = await GetPlayerMeta(store, props.id, displayCounter)
|
|
||||||
if (data.playerMeta === null)
|
|
||||||
data.playerMeta = {}
|
|
||||||
})
|
|
||||||
|
|
||||||
// watch(() => data.playerMeta, () => {
|
|
||||||
// console.log(data.playerMeta)
|
|
||||||
// })
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
const height = window.innerHeight - NAV_HEIGHT - FOOTER_HEIGHT
|
|
||||||
const wrapper = document.querySelector('.wrapper')
|
|
||||||
wrapper.style.minHeight = height + 'px'
|
|
||||||
|
|
||||||
await GetPlayer()
|
|
||||||
|
|
||||||
data.playerMeta = await GetPlayerMeta(store, props.id, displayCounter)
|
|
||||||
if (data.playerMeta === null)
|
|
||||||
data.playerMeta = {}
|
|
||||||
|
|
||||||
scrollToPos(store.state.scroll_state)
|
|
||||||
|
|
||||||
// console.log(store.state.playerDetails)
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
store.commit('changeScrollState', window.scrollY)
|
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
|
||||||
if (to.fullPath.match('/player/') && from.fullPath.match('/player/')) {
|
|
||||||
store.commit('changeScrollState', 0)
|
|
||||||
}
|
|
||||||
next()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
window.onresize = () => {
|
|
||||||
pHeight.value = getWindowHeight()
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
store,
|
|
||||||
pHeight,
|
|
||||||
props,
|
|
||||||
TrackPlayer,
|
|
||||||
RefreshData,
|
|
||||||
TrackMe,
|
|
||||||
GetWinLoss,
|
|
||||||
DisplayRank,
|
|
||||||
constructAvatarUrl,
|
|
||||||
FormatVacDate,
|
|
||||||
FixMapName,
|
|
||||||
GoToPlayer,
|
|
||||||
MatchNotParsedTime,
|
|
||||||
scrollToPos,
|
|
||||||
setMoreMatches
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.wrapper {
|
|
||||||
.load-more {
|
|
||||||
padding: 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trackme-btn,
|
|
||||||
.refresh-btn {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-btn {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
.fa-refresh {
|
|
||||||
color: var(--bs-warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa {
|
|
||||||
font-size: 1.3rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
padding-top: 10px;
|
|
||||||
|
|
||||||
.badges {
|
|
||||||
height: 30px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: auto;
|
|
||||||
height: 100%;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
border-radius: 50%;
|
|
||||||
height: 150px;
|
|
||||||
width: 150px;
|
|
||||||
box-shadow: 0 0 10px black;
|
|
||||||
|
|
||||||
&.tracked {
|
|
||||||
box-shadow: 0 0 20px 5px var(--bs-success);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa {
|
|
||||||
font-size: .75rem;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
max-width: 500px;
|
|
||||||
|
|
||||||
.wlt-win, .wlt-loss, .wlt-tie {
|
|
||||||
text-align: start;
|
|
||||||
max-width: 70px;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wlt-tie-rate, .wlt-win-rate {
|
|
||||||
text-align: end;
|
|
||||||
max-width: 90px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.match-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
|
|
||||||
.matches {
|
|
||||||
width: 75%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-info-container {
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
.card {
|
|
||||||
.avatar {
|
|
||||||
height: 75px !important;
|
|
||||||
width: 75px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.trackme-btn,
|
|
||||||
.refresh-btn {
|
|
||||||
top: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-btn {
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
.fa {
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 991px) {
|
|
||||||
.card .avatar {
|
|
||||||
height: 120px;
|
|
||||||
width: 120px;
|
|
||||||
}
|
|
||||||
.match-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 0;
|
|
||||||
|
|
||||||
.matches {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-info-container {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
647
src/views/PlayerView.vue
Normal file
647
src/views/PlayerView.vue
Normal file
@@ -0,0 +1,647 @@
|
|||||||
|
<template>
|
||||||
|
<div :style="{ minHeight: pHeight + 'px' }" class="wrapper">
|
||||||
|
<div class="container-lg">
|
||||||
|
<div v-if="playerDetailsStore.playerDetails.name">
|
||||||
|
<div class="card mb-3 bg-transparent border-0">
|
||||||
|
<div class="row g-0">
|
||||||
|
<div class="img-container col-md-2 pt-3">
|
||||||
|
<img
|
||||||
|
:class="data.tracked ? 'tracked' : ''"
|
||||||
|
:src="
|
||||||
|
constructAvatarUrl(
|
||||||
|
playerDetailsStore.playerDetails.avatar,
|
||||||
|
'full'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
:title="data.tracked ? 'Tracked' : ''"
|
||||||
|
alt="Player avatar"
|
||||||
|
class="img-fluid avatar"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8 d-flex">
|
||||||
|
<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"
|
||||||
|
>{{ playerDetailsStore.playerDetails.name }}
|
||||||
|
<i class="fa fa-steam"></i>
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
<table class="table table-borderless text-center">
|
||||||
|
<tr>
|
||||||
|
<th class="wlt-win text-uppercase text-muted">Wins</th>
|
||||||
|
<th class="wlt-loss text-uppercase text-muted">Losses</th>
|
||||||
|
<th class="wlt-tie text-uppercase text-muted">Ties</th>
|
||||||
|
<th class="wlt-win-rate text-uppercase text-muted">
|
||||||
|
Win-Rate
|
||||||
|
</th>
|
||||||
|
<th class="wlt-tie-rate text-uppercase text-muted">
|
||||||
|
Tie-Rate
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="wlt-win">{{ data.match_stats.win }}</td>
|
||||||
|
<td class="wlt-loss">{{ data.match_stats.loss }}</td>
|
||||||
|
<td class="wlt-tie">{{ data.match_stats.tie }}</td>
|
||||||
|
<td class="wlt-win-rate">
|
||||||
|
{{
|
||||||
|
data.match_stats.win > 0
|
||||||
|
? (
|
||||||
|
(data.match_stats.win / data.match_stats.total) *
|
||||||
|
100
|
||||||
|
).toFixed(0)
|
||||||
|
: 0
|
||||||
|
}}%
|
||||||
|
</td>
|
||||||
|
<td class="wlt-tie-rate">
|
||||||
|
{{
|
||||||
|
data.match_stats.tie > 0
|
||||||
|
? (
|
||||||
|
(data.match_stats.tie / data.match_stats.total) *
|
||||||
|
100
|
||||||
|
).toFixed(0)
|
||||||
|
: 0
|
||||||
|
}}%
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="badges">
|
||||||
|
<img
|
||||||
|
v-if="playerDetailsStore.playerDetails.vac"
|
||||||
|
:title="
|
||||||
|
'VAC-Ban: ' +
|
||||||
|
FormatVacDate(
|
||||||
|
playerDetailsStore.playerDetails.vacDate,
|
||||||
|
matchDetailsStore.matchDetails.date
|
||||||
|
)
|
||||||
|
"
|
||||||
|
alt="Vac banned"
|
||||||
|
src="/images/icons/vac_banned.svg"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
v-if="playerDetailsStore.playerDetails.gameBan"
|
||||||
|
:title="
|
||||||
|
'Game-Ban: ' +
|
||||||
|
FormatVacDate(
|
||||||
|
playerDetailsStore.playerDetails.gameBanDate,
|
||||||
|
matchDetailsStore.matchDetails.date
|
||||||
|
)
|
||||||
|
"
|
||||||
|
alt="Game banned"
|
||||||
|
src="/images/icons/game_banned.svg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!data.tracked" class="dropdown trackme-btn">
|
||||||
|
<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)"
|
||||||
|
required
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ShareCode input -->
|
||||||
|
<div class="form-outline mb-2">
|
||||||
|
<input
|
||||||
|
id="track-sharecode"
|
||||||
|
v-model="data.userData.sharecode"
|
||||||
|
:placeholder="
|
||||||
|
store.state.playerDetails.matches
|
||||||
|
? 'ShareCode (optional)'
|
||||||
|
: 'ShareCode (required)'
|
||||||
|
"
|
||||||
|
:required="!store.state.playerDetails.matches"
|
||||||
|
class="form-control bg-secondary"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-outline mb-4">
|
||||||
|
<small>
|
||||||
|
<a
|
||||||
|
href="https://help.steampowered.com/en/wizard/HelpWithGameIssue/?appid=730&issueid=128"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Here you can find your AuthCode and ShareCode
|
||||||
|
</a>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Submit button -->
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-warning border-2"
|
||||||
|
type="submit"
|
||||||
|
@click.prevent="TrackPlayer"
|
||||||
|
>
|
||||||
|
TrackMe
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="data.tracked"
|
||||||
|
class="refresh-btn"
|
||||||
|
title="Refresh Match-List"
|
||||||
|
@click="RefreshData"
|
||||||
|
>
|
||||||
|
<i class="fa fa-refresh fa-2x"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="match-container d-flex">
|
||||||
|
<div class="matches">
|
||||||
|
<MatchesTable
|
||||||
|
v-if="playerDetailsStore.playerDetails.matches"
|
||||||
|
:matches="playerDetailsStore.playerDetails.matches"
|
||||||
|
color-front
|
||||||
|
/>
|
||||||
|
<h5 v-else>Track yourself to see your matches</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="playerDetailsStore.playerDetails.matches"
|
||||||
|
class="side-info-container"
|
||||||
|
>
|
||||||
|
<PlayerSideInfo :player_meta="data.playerMeta" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="load-more col-lg-9 col-md-12 text-center">
|
||||||
|
<button
|
||||||
|
v-if="data.match_stats.total !== data.matches.length"
|
||||||
|
:key="scrollToPos(scrollStateStore.scrollState)"
|
||||||
|
class="btn border-2 btn-outline-info"
|
||||||
|
@click="setMoreMatches"
|
||||||
|
>
|
||||||
|
Load More
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="text-center pt-5">
|
||||||
|
<h3>Player-Page</h3>
|
||||||
|
<hr />
|
||||||
|
<h6>There seems to be a problem loading the player</h6>
|
||||||
|
<h6>Please try again later</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
onBeforeMount,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onMounted,
|
||||||
|
reactive,
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
} from "vue";
|
||||||
|
import {
|
||||||
|
constructAvatarUrl,
|
||||||
|
FormatVacDate,
|
||||||
|
GetPlayerMeta,
|
||||||
|
GetUser,
|
||||||
|
LoadImage,
|
||||||
|
LoadMoreMatches,
|
||||||
|
MatchNotParsedTime,
|
||||||
|
ProcessName,
|
||||||
|
SaveLastVisitedToLocalStorage,
|
||||||
|
scrollToPos,
|
||||||
|
setAppDivBackground,
|
||||||
|
setBgImgDisplay,
|
||||||
|
setTitle,
|
||||||
|
TrackMe,
|
||||||
|
} from "@/utils";
|
||||||
|
import { FOOTER_HEIGHT, NAV_HEIGHT } from "@/constants";
|
||||||
|
import MatchesTable from "@/components/MatchesTable.vue";
|
||||||
|
import router from "@/router";
|
||||||
|
import PlayerSideInfo from "@/components/PlayerSideInfo.vue";
|
||||||
|
import { StatusCodes as STATUS } from "http-status-codes";
|
||||||
|
import { usePlayerDetailsStore } from "@/stores/playerDetails";
|
||||||
|
import type { LocalStoragePlayer, Match, PlayerMeta } from "@/types";
|
||||||
|
import { useSearchParamsStore } from "@/stores/searchParams";
|
||||||
|
import { useInfoStateStore } from "@/stores/infoState";
|
||||||
|
import { useScrollStateStore } from "@/stores/scrollState";
|
||||||
|
import { useMatchDetailsStore } from "@/stores/matchDetails";
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
const pHeight = ref(0);
|
||||||
|
const displayCounter = 3;
|
||||||
|
|
||||||
|
const data = reactive({
|
||||||
|
userData: {
|
||||||
|
authcode: "",
|
||||||
|
sharecode: "",
|
||||||
|
},
|
||||||
|
tracked: false,
|
||||||
|
matches: [] as Match[],
|
||||||
|
match_stats: {
|
||||||
|
loss: 0,
|
||||||
|
win: 0,
|
||||||
|
tie: 0,
|
||||||
|
total: 0,
|
||||||
|
},
|
||||||
|
playerMeta: {} as PlayerMeta,
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
id: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const playerDetailsStore = usePlayerDetailsStore();
|
||||||
|
const matchDetailsStore = useMatchDetailsStore();
|
||||||
|
const searchParamsStore = useSearchParamsStore();
|
||||||
|
const scrollStateStore = useScrollStateStore();
|
||||||
|
const infoStateStore = useInfoStateStore();
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
if (Object.entries(playerDetailsStore.playerDetails).length === 0) {
|
||||||
|
GetPlayer();
|
||||||
|
} else {
|
||||||
|
// console.log(store.state.playerDetails)
|
||||||
|
SetPlayerData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const SetPlayerData = async () => {
|
||||||
|
data.tracked = playerDetailsStore.playerDetails.tracked;
|
||||||
|
if (playerDetailsStore.playerDetails.matches)
|
||||||
|
data.matches = playerDetailsStore.playerDetails.matches;
|
||||||
|
if (playerDetailsStore.playerDetails.matchStats) {
|
||||||
|
data.match_stats.loss =
|
||||||
|
playerDetailsStore.playerDetails.matchStats.loss || 0;
|
||||||
|
data.match_stats.win = playerDetailsStore.playerDetails.matchStats.win || 0;
|
||||||
|
data.match_stats.tie = playerDetailsStore.playerDetails.matchStats.tie || 0;
|
||||||
|
data.match_stats.total =
|
||||||
|
data.match_stats.loss + data.match_stats.win + data.match_stats.tie;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchParamsStore.id64 = playerDetailsStore.playerDetails.steamid64;
|
||||||
|
searchParamsStore.vanity_url =
|
||||||
|
playerDetailsStore.playerDetails.vanityUrl || "";
|
||||||
|
|
||||||
|
if (playerDetailsStore.playerDetails.matches) {
|
||||||
|
if (data.matches[0].map) {
|
||||||
|
await LoadImage(data.matches[0].map);
|
||||||
|
} else if (
|
||||||
|
!data.matches[0].map &&
|
||||||
|
MatchNotParsedTime(data.matches[0].date) &&
|
||||||
|
data.matches[1].map
|
||||||
|
) {
|
||||||
|
await LoadImage(data.matches[1].map);
|
||||||
|
} else {
|
||||||
|
await LoadImage("random");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await LoadImage("random");
|
||||||
|
}
|
||||||
|
|
||||||
|
setAppDivBackground("rgba(0, 0, 0, 0.7)", HTMLDivElement);
|
||||||
|
setBgImgDisplay("initial", HTMLImageElement);
|
||||||
|
|
||||||
|
const localPlayer = reactive<LocalStoragePlayer>({
|
||||||
|
steamId64: "",
|
||||||
|
vanityUrl: "",
|
||||||
|
name: "",
|
||||||
|
avatar: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
localPlayer.steamId64 = playerDetailsStore.playerDetails.steamid64;
|
||||||
|
localPlayer.vanityUrl = playerDetailsStore.playerDetails.vanity_url || "";
|
||||||
|
localPlayer.name = playerDetailsStore.playerDetails.name || "";
|
||||||
|
localPlayer.avatar = constructAvatarUrl(
|
||||||
|
playerDetailsStore.playerDetails.avatar || "",
|
||||||
|
"medium"
|
||||||
|
);
|
||||||
|
SaveLastVisitedToLocalStorage(localPlayer);
|
||||||
|
|
||||||
|
setTitle(playerDetailsStore.playerDetails.name || "");
|
||||||
|
};
|
||||||
|
|
||||||
|
const GetPlayer = async (reset = false) => {
|
||||||
|
if (props.id) {
|
||||||
|
const [resData, info] = await GetUser(props.id);
|
||||||
|
|
||||||
|
if (info.message !== "") infoStateStore.addInfo(info);
|
||||||
|
|
||||||
|
if (resData !== null) {
|
||||||
|
if (
|
||||||
|
resData.steamid64 !== playerDetailsStore.playerDetails.steamid64 ||
|
||||||
|
reset
|
||||||
|
) {
|
||||||
|
resData.name = ProcessName(resData.name);
|
||||||
|
|
||||||
|
playerDetailsStore.$reset();
|
||||||
|
playerDetailsStore.playerDetails = resData;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SetPlayerData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setMoreMatches = async () => {
|
||||||
|
const [res, info] = await LoadMoreMatches(
|
||||||
|
playerDetailsStore.playerDetails.steamid64,
|
||||||
|
data.matches[data.matches.length - 1].date
|
||||||
|
);
|
||||||
|
|
||||||
|
if (info.message !== "") infoStateStore.addInfo(info);
|
||||||
|
if (res !== null) await res.matches?.forEach((e) => data.matches.push(e));
|
||||||
|
|
||||||
|
scrollToPos(window.scrollY);
|
||||||
|
|
||||||
|
// console.log(store.state.playerDetails)
|
||||||
|
};
|
||||||
|
|
||||||
|
const RefreshData = async () => {
|
||||||
|
const refreshButton = document.querySelector(
|
||||||
|
".refresh-btn .fa"
|
||||||
|
) as HTMLElement;
|
||||||
|
refreshButton.classList.add("fa-spin");
|
||||||
|
refreshButton.classList.add("fa-fw");
|
||||||
|
refreshButton.classList.remove("fa-refresh");
|
||||||
|
refreshButton.classList.add("fa-spinner");
|
||||||
|
scrollToPos(0);
|
||||||
|
|
||||||
|
await GetPlayer(true).then(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
refreshButton.classList.remove("fa-spin");
|
||||||
|
refreshButton.classList.remove("fa-fw");
|
||||||
|
refreshButton.classList.add("fa-refresh");
|
||||||
|
refreshButton.classList.remove("fa-spinner");
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [resData, info] = await GetPlayerMeta(props.id, displayCounter);
|
||||||
|
|
||||||
|
if (info.message !== "") infoStateStore.addInfo(info);
|
||||||
|
if (resData !== null) {
|
||||||
|
data.playerMeta = resData;
|
||||||
|
} else {
|
||||||
|
data.playerMeta = {} as PlayerMeta;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const TrackPlayer = async () => {
|
||||||
|
let message = "";
|
||||||
|
|
||||||
|
if (data.matches.length === 0) {
|
||||||
|
if (data.userData.sharecode === "") {
|
||||||
|
message = "Sharecode is missing";
|
||||||
|
}
|
||||||
|
if (data.userData.authcode === "") {
|
||||||
|
message = "Authcode is missing";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (data.userData.authcode === "") {
|
||||||
|
message = "Authcode is missing";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message !== "") {
|
||||||
|
infoStateStore.addInfo({
|
||||||
|
statusCode: STATUS.IM_A_TEAPOT,
|
||||||
|
message: message,
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const info = await TrackMe(
|
||||||
|
playerDetailsStore.playerDetails.steamid64,
|
||||||
|
data.userData.authcode,
|
||||||
|
data.userData.sharecode
|
||||||
|
);
|
||||||
|
|
||||||
|
if (info.message !== "") {
|
||||||
|
infoStateStore.addInfo(info);
|
||||||
|
} else if (info.statusCode === STATUS.ACCEPTED) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.id,
|
||||||
|
async () => {
|
||||||
|
await GetPlayer();
|
||||||
|
const [res, info] = await GetPlayerMeta(props.id, displayCounter);
|
||||||
|
|
||||||
|
if (info.message !== "") infoStateStore.addInfo(info);
|
||||||
|
if (res !== null) {
|
||||||
|
data.playerMeta = res;
|
||||||
|
} else {
|
||||||
|
data.playerMeta = {} as PlayerMeta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const height = window.innerHeight - NAV_HEIGHT - FOOTER_HEIGHT;
|
||||||
|
const wrapper = document.querySelector(".wrapper") as HTMLDivElement;
|
||||||
|
wrapper.style.minHeight = height + "px";
|
||||||
|
|
||||||
|
await GetPlayer();
|
||||||
|
|
||||||
|
const [res, info] = await GetPlayerMeta(props.id, displayCounter);
|
||||||
|
if (info.message !== "") infoStateStore.addInfo(info);
|
||||||
|
if (res !== null) {
|
||||||
|
data.playerMeta = res;
|
||||||
|
} else {
|
||||||
|
data.playerMeta = {} as PlayerMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToPos(scrollStateStore.scrollState);
|
||||||
|
|
||||||
|
// console.log(store.state.playerDetails)
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
scrollStateStore.scrollState = window.scrollY;
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
if (to.fullPath.match("/player/") && from.fullPath.match("/player/")) {
|
||||||
|
scrollStateStore.$reset();
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
window.onresize = () => {
|
||||||
|
pHeight.value = getWindowHeight();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.wrapper {
|
||||||
|
.load-more {
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trackme-btn,
|
||||||
|
.refresh-btn {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
.fa-refresh {
|
||||||
|
color: var(--bs-warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding-top: 10px;
|
||||||
|
|
||||||
|
.badges {
|
||||||
|
height: 30px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: auto;
|
||||||
|
height: 100%;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 150px;
|
||||||
|
width: 150px;
|
||||||
|
box-shadow: 0 0 10px black;
|
||||||
|
|
||||||
|
&.tracked {
|
||||||
|
box-shadow: 0 0 20px 5px var(--bs-success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
max-width: 500px;
|
||||||
|
|
||||||
|
.wlt-win,
|
||||||
|
.wlt-loss,
|
||||||
|
.wlt-tie {
|
||||||
|
text-align: start;
|
||||||
|
max-width: 70px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wlt-tie-rate,
|
||||||
|
.wlt-win-rate {
|
||||||
|
text-align: end;
|
||||||
|
max-width: 90px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
.matches {
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-info-container {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.card {
|
||||||
|
.avatar {
|
||||||
|
height: 75px !important;
|
||||||
|
width: 75px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.trackme-btn,
|
||||||
|
.refresh-btn {
|
||||||
|
top: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn {
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
.fa {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 991px) {
|
||||||
|
.card .avatar {
|
||||||
|
height: 120px;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
.match-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0;
|
||||||
|
|
||||||
|
.matches {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-info-container {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -4,13 +4,3 @@
|
|||||||
<h4 class="mt-4">The page you were looking for was not found!</h4>
|
<h4 class="mt-4">The page you were looking for was not found!</h4>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: "404"
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -4,13 +4,3 @@
|
|||||||
<h4 class="mt-4">An internal server error occurred!</h4>
|
<h4 class="mt-4">An internal server error occurred!</h4>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: "500"
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -4,13 +4,3 @@
|
|||||||
<h4 class="mt-4">You reached a bad gateway!</h4>
|
<h4 class="mt-4">You reached a bad gateway!</h4>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: "502"
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||||
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"lib": ["es2021"]
|
||||||
|
},
|
||||||
|
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.vite-config.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
8
tsconfig.vite-config.json
Normal file
8
tsconfig.vite-config.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.node.json",
|
||||||
|
"include": ["vite.config.*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"types": ["node"]
|
||||||
|
}
|
||||||
|
}
|
||||||
14
vite.config.ts
Normal file
14
vite.config.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { fileURLToPath, URL } from 'url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
const Dotenv = require('dotenv-webpack');
|
|
||||||
process.env.VUE_APP_VERSION = require('./package.json').version
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
configureWebpack: {
|
|
||||||
plugins: [
|
|
||||||
new Dotenv()
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user