refactor!: Clear out legacy code for rewrite.
This commit is contained in:
@@ -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
|
|
||||||
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-parser'
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
286
.gitignore
vendored
286
.gitignore
vendored
@@ -1,286 +0,0 @@
|
|||||||
# Created by https://www.toptal.com/developers/gitignore/api/webstorm+all,yarn,windows,linux,node,vuejs
|
|
||||||
# Edit at https://www.toptal.com/developers/gitignore?templates=webstorm+all,yarn,windows,linux,node,vuejs
|
|
||||||
|
|
||||||
### Linux ###
|
|
||||||
*~
|
|
||||||
|
|
||||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
|
||||||
.fuse_hidden*
|
|
||||||
|
|
||||||
# KDE directory preferences
|
|
||||||
.directory
|
|
||||||
|
|
||||||
# Linux trash folder which might appear on any partition or disk
|
|
||||||
.Trash-*
|
|
||||||
|
|
||||||
# .nfs files are created when an open file is removed but is still being accessed
|
|
||||||
.nfs*
|
|
||||||
|
|
||||||
### Node ###
|
|
||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
.pnpm-debug.log*
|
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
*.lcov
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
|
||||||
jspm_packages/
|
|
||||||
|
|
||||||
# Snowpack dependency directory (https://snowpack.dev/)
|
|
||||||
web_modules/
|
|
||||||
|
|
||||||
# TypeScript cache
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Microbundle cache
|
|
||||||
.rpt2_cache/
|
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variables file
|
|
||||||
.env
|
|
||||||
.env.test
|
|
||||||
.env.production
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
|
||||||
.cache
|
|
||||||
.parcel-cache
|
|
||||||
|
|
||||||
# Next.js build output
|
|
||||||
.next
|
|
||||||
out
|
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
|
||||||
.nuxt
|
|
||||||
dist
|
|
||||||
|
|
||||||
# Gatsby files
|
|
||||||
.cache/
|
|
||||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
|
||||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
||||||
# public
|
|
||||||
|
|
||||||
# vuepress build output
|
|
||||||
.vuepress/dist
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
.dynamodb/
|
|
||||||
|
|
||||||
# TernJS port file
|
|
||||||
.tern-port
|
|
||||||
|
|
||||||
# Stores VSCode versions used for testing VSCode extensions
|
|
||||||
.vscode-test
|
|
||||||
|
|
||||||
# yarn v2
|
|
||||||
.yarn/cache
|
|
||||||
.yarn/unplugged
|
|
||||||
.yarn/build-state.yml
|
|
||||||
.yarn/install-state.gz
|
|
||||||
.pnp.*
|
|
||||||
|
|
||||||
### Node Patch ###
|
|
||||||
# Serverless Webpack directories
|
|
||||||
.webpack/
|
|
||||||
|
|
||||||
### Vuejs ###
|
|
||||||
# Recommended template: Node.gitignore
|
|
||||||
|
|
||||||
dist/
|
|
||||||
npm-debug.log
|
|
||||||
yarn-error.log
|
|
||||||
|
|
||||||
### WebStorm+all ###
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# AWS User-specific
|
|
||||||
.idea/**/aws.xml
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.idea/**/contentModel.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
### WebStorm+all Patch ###
|
|
||||||
# Ignores the whole .idea folder and all .iml files
|
|
||||||
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
|
||||||
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
|
||||||
|
|
||||||
*.iml
|
|
||||||
modules.xml
|
|
||||||
.idea/misc.xml
|
|
||||||
*.ipr
|
|
||||||
|
|
||||||
# Sonarlint plugin
|
|
||||||
.idea/sonarlint
|
|
||||||
|
|
||||||
### Windows ###
|
|
||||||
# Windows thumbnail cache files
|
|
||||||
Thumbs.db
|
|
||||||
Thumbs.db:encryptable
|
|
||||||
ehthumbs.db
|
|
||||||
ehthumbs_vista.db
|
|
||||||
|
|
||||||
# Dump file
|
|
||||||
*.stackdump
|
|
||||||
|
|
||||||
# Folder config file
|
|
||||||
[Dd]esktop.ini
|
|
||||||
|
|
||||||
# Recycle Bin used on file shares
|
|
||||||
$RECYCLE.BIN/
|
|
||||||
|
|
||||||
# Windows Installer files
|
|
||||||
*.cab
|
|
||||||
*.msi
|
|
||||||
*.msix
|
|
||||||
*.msm
|
|
||||||
*.msp
|
|
||||||
|
|
||||||
# Windows shortcuts
|
|
||||||
*.lnk
|
|
||||||
|
|
||||||
### yarn ###
|
|
||||||
# https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored
|
|
||||||
|
|
||||||
.yarn/*
|
|
||||||
!.yarn/releases
|
|
||||||
!.yarn/plugins
|
|
||||||
!.yarn/sdks
|
|
||||||
!.yarn/versions
|
|
||||||
|
|
||||||
# if you are NOT using Zero-installs, then:
|
|
||||||
# comment the following lines
|
|
||||||
#!.yarn/cache
|
|
||||||
|
|
||||||
# and uncomment the following lines
|
|
||||||
.pnp.*
|
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/webstorm+all,yarn,windows,linux,node,vuejs
|
|
||||||
|
|
||||||
|
|
||||||
363
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
363
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
File diff suppressed because one or more lines are too long
873
.yarn/releases/yarn-3.4.1.cjs
vendored
873
.yarn/releases/yarn-3.4.1.cjs
vendored
File diff suppressed because one or more lines are too long
@@ -1,7 +0,0 @@
|
|||||||
nodeLinker: node-modules
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
|
||||||
spec: "@yarnpkg/plugin-interactive-tools"
|
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-3.4.1.cjs
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
'@vue/cli-plugin-babel/preset'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
46
package.json
46
package.json
@@ -1,46 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "csgowtf",
|
|
||||||
"version": "1.0.9",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"serve": "vue-cli-service serve",
|
|
||||||
"build": "vue-cli-service build --mode production",
|
|
||||||
"lint": "vue-cli-service lint"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@fontsource/open-sans": "^4.5.14",
|
|
||||||
"@fontsource/orbitron": "^4.5.11",
|
|
||||||
"@popperjs/core": "^2.11.6",
|
|
||||||
"axios": "^1.3.4",
|
|
||||||
"bootstrap": "^5.2.3",
|
|
||||||
"core-js": "^3.29.0",
|
|
||||||
"dotenv-webpack": "^8.0.1",
|
|
||||||
"echarts": "^5.4.1",
|
|
||||||
"fork-awesome": "^1.2.0",
|
|
||||||
"http-status-codes": "^2.2.0",
|
|
||||||
"iso-639-1": "^2.1.15",
|
|
||||||
"jquery": "^3.6.3",
|
|
||||||
"luxon": "^3.2.1",
|
|
||||||
"string-sanitizer": "^2.0.2",
|
|
||||||
"vue": "^3.2.47",
|
|
||||||
"vue-matomo": "^4.2.0",
|
|
||||||
"vue-router": "^4.1.6",
|
|
||||||
"vue3-cookies": "^1.0.6",
|
|
||||||
"vuex": "^4.1.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@babel/core": "^7.21.0",
|
|
||||||
"@babel/eslint-parser": "^7.19.1",
|
|
||||||
"@vue/cli-plugin-babel": "~5.0.8",
|
|
||||||
"@vue/cli-plugin-eslint": "~5.0.8",
|
|
||||||
"@vue/cli-plugin-router": "~5.0.8",
|
|
||||||
"@vue/cli-plugin-vuex": "~5.0.8",
|
|
||||||
"@vue/cli-service": "~5.0.8",
|
|
||||||
"@vue/compiler-sfc": "^3.2.47",
|
|
||||||
"eslint": "^8.35.0",
|
|
||||||
"eslint-plugin-vue": "^9.9.0",
|
|
||||||
"sass": "^1.58.3",
|
|
||||||
"sass-loader": "^13.2.0"
|
|
||||||
},
|
|
||||||
"packageManager": "yarn@3.4.1"
|
|
||||||
}
|
|
||||||
69
src/App.vue
69
src/App.vue
@@ -1,69 +0,0 @@
|
|||||||
<template>
|
|
||||||
<img alt="" class="bg-img" src="">
|
|
||||||
<header>
|
|
||||||
<CompNav/>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<div :style="{height: offset + 'px'}"/>
|
|
||||||
<InfoModal/>
|
|
||||||
<router-view name="main"/>
|
|
||||||
</main>
|
|
||||||
<footer class="mt-auto">
|
|
||||||
<CompFooter/>
|
|
||||||
</footer>
|
|
||||||
<CookieConsentBtn id="cookie-btn"/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {onMounted, ref} from "vue";
|
|
||||||
import InfoModal from "@/components/InfoModal";
|
|
||||||
import CompFooter from "@/components/CompFooter";
|
|
||||||
import CompNav from "@/components/CompNav";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {CompNav, CompFooter, InfoModal},
|
|
||||||
setup() {
|
|
||||||
const offset = ref(0)
|
|
||||||
|
|
||||||
const setOffset = () => {
|
|
||||||
return document.getElementsByTagName('nav')[0].clientHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
const setBgHeight = () => {
|
|
||||||
document.querySelector('.bg-img').style.height = document.documentElement.clientHeight + 'px'
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onresize = () => {
|
|
||||||
offset.value = setOffset()
|
|
||||||
setBgHeight()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
offset.value = setOffset()
|
|
||||||
setBgHeight()
|
|
||||||
})
|
|
||||||
|
|
||||||
return {offset}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@font-face {
|
|
||||||
font-family: "Obitron";
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-img {
|
|
||||||
z-index: -1;
|
|
||||||
position: fixed;
|
|
||||||
width: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
#cookie-btn {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 30px;
|
|
||||||
right: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -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: "CompDetails",
|
|
||||||
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>
|
|
||||||
@@ -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://somegit.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://somegit.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: "CompFooter",
|
|
||||||
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>
|
|
||||||
@@ -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: 'CompNav',
|
|
||||||
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>
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="damage-site">
|
|
||||||
<div class="total-damage">
|
|
||||||
<h3 class="text-center mt-2">Total Damage</h3>
|
|
||||||
<TotalDamage/>
|
|
||||||
</div>
|
|
||||||
<div class="hitgroup">
|
|
||||||
<!-- <h3 class="text-center">Damage by Hitgroup</h3>-->
|
|
||||||
<HitgroupPuppet :equipment_map="data.equipment_map" :stats="data.stats" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import HitgroupPuppet from '@/components/HitgroupPuppet'
|
|
||||||
import TotalDamage from "@/components/TotalDamage"
|
|
||||||
import {onMounted, reactive} from "vue";
|
|
||||||
import {useStore} from "vuex";
|
|
||||||
import {GetWeaponDmg} from "@/utils";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "DamageSite.vue",
|
|
||||||
components: {HitgroupPuppet, TotalDamage},
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const data = reactive({
|
|
||||||
equipment_map: {},
|
|
||||||
stats: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
const getWeaponDamage = async () => {
|
|
||||||
const resData = await GetWeaponDmg(store, store.state.matchDetails.match_id)
|
|
||||||
if (resData !== null) {
|
|
||||||
data.equipment_map = resData.equipment_map
|
|
||||||
data.stats = resData.stats
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getWeaponDamage()
|
|
||||||
})
|
|
||||||
|
|
||||||
return {data}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.damage-site {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="economy">
|
|
||||||
<h3 class="text-center mt-2">Economy</h3>
|
|
||||||
<div class="flexbreak"></div>
|
|
||||||
<div id="economy-graph"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import {GetPlayerValue} from "@/utils";
|
|
||||||
import {useStore} from "vuex";
|
|
||||||
import {onBeforeMount, onMounted, onUnmounted, reactive, ref, watch} from "vue";
|
|
||||||
|
|
||||||
import * as echarts from 'echarts/core';
|
|
||||||
import {
|
|
||||||
GridComponent,
|
|
||||||
MarkAreaComponent,
|
|
||||||
TitleComponent,
|
|
||||||
TooltipComponent,
|
|
||||||
VisualMapComponent
|
|
||||||
} from 'echarts/components';
|
|
||||||
import {LineChart} from 'echarts/charts';
|
|
||||||
import {UniversalTransition} from 'echarts/features';
|
|
||||||
import {CanvasRenderer} from 'echarts/renderers';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "EqValueGraph",
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
let myChart1, max_rounds
|
|
||||||
let valueList = []
|
|
||||||
let dataList = []
|
|
||||||
const width = ref(window.innerWidth >= 800 && window.innerWidth <= 1200 ? window.innerWidth : window.innerWidth < 800 ? 800 : 1200)
|
|
||||||
const height = ref(width.value * 1 / 3)
|
|
||||||
|
|
||||||
const data = reactive({
|
|
||||||
rounds: {},
|
|
||||||
team: [],
|
|
||||||
eq_team_1: [],
|
|
||||||
eq_team_2: [],
|
|
||||||
eq_team_player_1: [],
|
|
||||||
eq_team_player_2: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
const getTeamPlayer = (stats, team) => {
|
|
||||||
let arr = []
|
|
||||||
for (let i = (team - 1) * 5; i < team * 5; i++) {
|
|
||||||
arr.push(stats[i].player.steamid64)
|
|
||||||
}
|
|
||||||
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseObject = async () => {
|
|
||||||
data.rounds = await GetPlayerValue(store, store.state.matchDetails.match_id)
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.economy {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
margin: 0 auto 3rem;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin-bottom: -1rem;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
h3 {
|
|
||||||
margin-left: 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 800px) and (min-width: 1199px) {
|
|
||||||
#economy-graph {
|
|
||||||
overflow: scroll;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,244 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="player-flash">
|
|
||||||
<h3 class="text-center mt-2">Flash</h3>
|
|
||||||
<div class="flex-break"></div>
|
|
||||||
<div class="toggle-btn">
|
|
||||||
<div @click="toggleShow">
|
|
||||||
<table class="table table-borderless text-muted">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="text-uppercase float-end" :class="toggle === 'duration' ? 'text-warning' : ''">Duration</span>
|
|
||||||
</td>
|
|
||||||
<td class="text-center">
|
|
||||||
<i id="toggle-off" class="fa fa-toggle-off show"></i>
|
|
||||||
<i id="toggle-on" class="fa fa-toggle-on"></i>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span class="text-uppercase float-start" :class="toggle === 'total' ? 'text-warning' : ''">Count</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-break"></div>
|
|
||||||
<div id="flash-chart-1"></div>
|
|
||||||
<div id="flash-chart-2"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import * as echarts from 'echarts/core';
|
|
||||||
import {GridComponent, LegendComponent, TooltipComponent} from 'echarts/components';
|
|
||||||
import {BarChart} from 'echarts/charts';
|
|
||||||
import {CanvasRenderer} from 'echarts/renderers';
|
|
||||||
import {onMounted, onUnmounted, ref, watch} from "vue";
|
|
||||||
import {checkStatEmpty, getPlayerArr} from "@/utils";
|
|
||||||
import {useStore} from "vuex";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "FlashChart",
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const toggle = ref('duration')
|
|
||||||
let myChart1, myChart2
|
|
||||||
const color = ['#bb792c', '#9bd270', '#eac42a']
|
|
||||||
const width = ref(window.innerWidth <= 600 ? window.innerWidth : 600)
|
|
||||||
const height = ref(width.value * 2 / 3)
|
|
||||||
|
|
||||||
const toggleShow = () => {
|
|
||||||
const offBtn = document.getElementById('toggle-off')
|
|
||||||
const onBtn = document.getElementById('toggle-on')
|
|
||||||
|
|
||||||
if (offBtn.classList.contains('show')) {
|
|
||||||
offBtn.classList.remove('show')
|
|
||||||
onBtn.classList.add('show')
|
|
||||||
toggle.value = 'total'
|
|
||||||
} else if (onBtn.classList.contains('show')) {
|
|
||||||
onBtn.classList.remove('show')
|
|
||||||
offBtn.classList.add('show')
|
|
||||||
toggle.value = 'duration'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const valueArr = (stats, team, toggle, prop) => {
|
|
||||||
if (['team', 'enemy', 'self'].indexOf(prop) > -1) {
|
|
||||||
let arr = []
|
|
||||||
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.reverse()
|
|
||||||
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const setOptions = (id, color) => {
|
|
||||||
return {
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
axisPointer: {
|
|
||||||
type: 'shadow',
|
|
||||||
shadowStyle: {
|
|
||||||
shadowBlur: 2,
|
|
||||||
shadowColor: 'rgba(255, 255, 255, .3)'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
left: '3%',
|
|
||||||
right: '4%',
|
|
||||||
bottom: '3%',
|
|
||||||
containLabel: true
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'value',
|
|
||||||
boundaryGap: [0, 0.01]
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'category',
|
|
||||||
data: getPlayerArr(store.state.matchDetails.stats, id, true)
|
|
||||||
},
|
|
||||||
color: color,
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'Enemy',
|
|
||||||
type: 'bar',
|
|
||||||
data: valueArr(store.state.matchDetails.stats, id, toggle, 'enemy'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Team',
|
|
||||||
type: 'bar',
|
|
||||||
data: valueArr(store.state.matchDetails.stats, id, toggle, 'team'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Self',
|
|
||||||
type: 'bar',
|
|
||||||
data: valueArr(store.state.matchDetails.stats, id, toggle, 'self'),
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const disposeCharts = () => {
|
|
||||||
if (myChart1 != null && myChart1 !== '' && myChart1 !== undefined) {
|
|
||||||
myChart1.dispose()
|
|
||||||
}
|
|
||||||
if (myChart2 != null && myChart2 !== '' && myChart2 !== undefined) {
|
|
||||||
myChart2.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildCharts = () => {
|
|
||||||
disposeCharts()
|
|
||||||
|
|
||||||
myChart1 = echarts.init(document.getElementById('flash-chart-1'), {}, {
|
|
||||||
width: width.value,
|
|
||||||
height: height.value
|
|
||||||
});
|
|
||||||
myChart1.setOption(setOptions(1, color));
|
|
||||||
|
|
||||||
myChart2 = echarts.init(document.getElementById('flash-chart-2'), {}, {
|
|
||||||
width: width.value,
|
|
||||||
height: height.value
|
|
||||||
});
|
|
||||||
myChart2.setOption(setOptions(2, color));
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (store.state.matchDetails.stats) {
|
|
||||||
echarts.use([
|
|
||||||
TooltipComponent,
|
|
||||||
GridComponent,
|
|
||||||
LegendComponent,
|
|
||||||
BarChart,
|
|
||||||
CanvasRenderer
|
|
||||||
]);
|
|
||||||
|
|
||||||
buildCharts()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
disposeCharts()
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => toggle.value, () => {
|
|
||||||
buildCharts()
|
|
||||||
})
|
|
||||||
|
|
||||||
window.onresize = () => {
|
|
||||||
if (window.innerWidth <= 600) {
|
|
||||||
width.value = window.innerWidth - 20
|
|
||||||
height.value = width.value * 2 / 3
|
|
||||||
|
|
||||||
buildCharts()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {toggleShow, toggle}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.player-flash {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
|
|
||||||
.flex-break {
|
|
||||||
flex-basis: 100%;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 1rem auto -1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-btn {
|
|
||||||
margin: 0 auto;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
table {
|
|
||||||
margin-top: 1rem;
|
|
||||||
|
|
||||||
td {
|
|
||||||
font-size: .8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
td:first-child,
|
|
||||||
td:last-child {
|
|
||||||
max-width: 80px;
|
|
||||||
width: 80px;
|
|
||||||
}
|
|
||||||
td:nth-child(2) {
|
|
||||||
max-width: 30px;
|
|
||||||
width: 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa {
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
&.show {
|
|
||||||
display: initial;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#flash-chart-1,
|
|
||||||
#flash-chart-2 {
|
|
||||||
flex-basis: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
.player-flash {
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,564 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="hitgroup pt-2">
|
|
||||||
<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="select-group mb-4">
|
|
||||||
<select v-if="store.state.playersArr" v-model="data.selectPlayer" class="form-select">
|
|
||||||
<option value="All">All</option>
|
|
||||||
<option value="Team 1">Team 1</option>
|
|
||||||
<option value="Team 2">Team 2</option>
|
|
||||||
<option disabled>───────────────────────────</option>
|
|
||||||
<option 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 : ''
|
|
||||||
}}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select v-if="data.selectPlayer !== ''" :key="data.selectPlayer" v-model="data.selectWeapon"
|
|
||||||
class="form-select">
|
|
||||||
<option class="select-hr" value="All">All</option>
|
|
||||||
<option disabled>───────────────────────────</option>
|
|
||||||
<option v-for="(value, index) in processPlayerWeapon()" :key="index" :value="value">
|
|
||||||
<!-- 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() }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="hitgroup-puppet"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="data.weaponDmg"
|
|
||||||
id="bar-graph"
|
|
||||||
class="w-auto"
|
|
||||||
:style="{
|
|
||||||
minWidth: dmgWidth + 'px'
|
|
||||||
}">
|
|
||||||
<table class="table table-borderless">
|
|
||||||
<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())"
|
|
||||||
style="width: 100px">
|
|
||||||
<img :alt="Object.values(value).toString()"
|
|
||||||
:src="DisplayWeapon(parseInt(Object.keys(value)[0]))"/>
|
|
||||||
</td>
|
|
||||||
<td v-if="index < 10 && (data.selectWeapon === 'All' || Object.keys(data.selectWeapon).toString() === Object.keys(value).toString())">
|
|
||||||
<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>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import * as echarts from 'echarts/core';
|
|
||||||
import {GeoComponent, TooltipComponent, VisualMapComponent} from 'echarts/components';
|
|
||||||
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 '@/utils'
|
|
||||||
|
|
||||||
import $ from 'jquery'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "HitgroupPuppet.vue",
|
|
||||||
props: {
|
|
||||||
equipment_map: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
stats: {
|
|
||||||
type: Array,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const data = reactive({
|
|
||||||
selectPlayer: 'All',
|
|
||||||
selectWeapon: 'All',
|
|
||||||
eq_map: [],
|
|
||||||
weaponDmg: []
|
|
||||||
})
|
|
||||||
|
|
||||||
let myChart1
|
|
||||||
|
|
||||||
const getWindowWidth = () => {
|
|
||||||
const windowWidth = window.innerWidth
|
|
||||||
if (windowWidth <= 750)
|
|
||||||
return windowWidth
|
|
||||||
else
|
|
||||||
return 650
|
|
||||||
}
|
|
||||||
|
|
||||||
const setDmgWidth = () => {
|
|
||||||
const windowWidth = getWindowWidth()
|
|
||||||
if (windowWidth >= 500)
|
|
||||||
return 500
|
|
||||||
else
|
|
||||||
return windowWidth - 10
|
|
||||||
}
|
|
||||||
|
|
||||||
const dmgWidth = ref(setDmgWidth())
|
|
||||||
|
|
||||||
const setHeight = () => {
|
|
||||||
const windowWidth = getWindowWidth()
|
|
||||||
if (windowWidth >= 751)
|
|
||||||
return windowWidth * 3 / 7.5
|
|
||||||
else if (windowWidth >= 501 && windowWidth <= 750)
|
|
||||||
return windowWidth * 3 / 6.5
|
|
||||||
else
|
|
||||||
return windowWidth * 3 / 5.5
|
|
||||||
}
|
|
||||||
|
|
||||||
const width = ref(getWindowWidth())
|
|
||||||
const height = ref(setHeight())
|
|
||||||
|
|
||||||
const processWeaponDmg = (id) => {
|
|
||||||
let value = ''
|
|
||||||
data.weaponDmg.forEach(w => {
|
|
||||||
if (Object.keys(w).toString() === id) {
|
|
||||||
value = Object.values(w).toString()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
const processPlayerWeapon = () => {
|
|
||||||
let arr = []
|
|
||||||
if (data.selectPlayer === 'All') {
|
|
||||||
props.stats.forEach(player => {
|
|
||||||
Object.values(player).forEach(enemies => {
|
|
||||||
Object.values(enemies).forEach(weapons => {
|
|
||||||
Object.values(weapons).forEach(weapon => {
|
|
||||||
arr.push(weapon[0])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else if (data.selectPlayer === 'Team 1') {
|
|
||||||
props.stats.forEach(player => {
|
|
||||||
store.state.playersArr.forEach(p => {
|
|
||||||
if (p.player.steamid64 === Object.keys(player).toString() && p.team_id === 1)
|
|
||||||
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)
|
|
||||||
Object.values(player).forEach(enemies => {
|
|
||||||
Object.values(enemies).forEach(weapons => {
|
|
||||||
Object.values(weapons).forEach(weapon => {
|
|
||||||
arr.push(weapon[0])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
props.stats.forEach(player => {
|
|
||||||
if (Object.keys(player).toString() === data.selectPlayer.steamid64) {
|
|
||||||
Object.values(player).forEach(enemies => {
|
|
||||||
Object.values(enemies).forEach(weapons => {
|
|
||||||
Object.values(weapons).forEach(weapon => {
|
|
||||||
arr.push(weapon[0])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const unique = arr.filter((a, b) => arr.indexOf(a) === b && a < 400)
|
|
||||||
|
|
||||||
let arr2 = []
|
|
||||||
|
|
||||||
unique.forEach(w => {
|
|
||||||
for (let weapon in props.equipment_map) {
|
|
||||||
if (parseInt(w) === parseInt(weapon)) {
|
|
||||||
let obj = {}
|
|
||||||
obj[w] = props.equipment_map[weapon]
|
|
||||||
arr2.push(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return arr2
|
|
||||||
}
|
|
||||||
|
|
||||||
const processDmg = (by = 'hitgroup') => {
|
|
||||||
let arr = []
|
|
||||||
if (data.selectPlayer && data.selectWeapon) {
|
|
||||||
switch (data.selectPlayer) {
|
|
||||||
case "All":
|
|
||||||
props.stats.forEach(player => {
|
|
||||||
Object.values(player).forEach(enemies => {
|
|
||||||
Object.values(enemies).forEach(weapons => {
|
|
||||||
Object.values(weapons).forEach(weapon => {
|
|
||||||
// 0: weapon
|
|
||||||
// 1: hitgroup
|
|
||||||
// 2: dmg
|
|
||||||
if (weapon) {
|
|
||||||
if (by === 'hitgroup') {
|
|
||||||
if (Object.values(weapon)[0] === parseInt(Object.keys(data.selectWeapon).toString())) {
|
|
||||||
let 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') {
|
|
||||||
if (Object.values(weapon)[0] === parseInt(Object.keys(data.selectWeapon).toString())) {
|
|
||||||
let 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;
|
|
||||||
|
|
||||||
case "Team 1":
|
|
||||||
props.stats.forEach(player => {
|
|
||||||
store.state.playersArr.forEach(p => {
|
|
||||||
if (p.player.steamid64 === Object.keys(player).toString() && p.team_id === 1)
|
|
||||||
Object.values(player).forEach(enemies => {
|
|
||||||
Object.values(enemies).forEach(weapons => {
|
|
||||||
Object.values(weapons).forEach(weapon => {
|
|
||||||
// 0: weapon
|
|
||||||
// 1: hitgroup
|
|
||||||
// 2: dmg
|
|
||||||
if (weapon) {
|
|
||||||
if (by === 'hitgroup') {
|
|
||||||
if (Object.values(weapon)[0] === parseInt(Object.keys(data.selectWeapon).toString())) {
|
|
||||||
let 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') {
|
|
||||||
if (Object.values(weapon)[0] === parseInt(Object.keys(data.selectWeapon).toString())) {
|
|
||||||
let 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;
|
|
||||||
|
|
||||||
case "Team 2":
|
|
||||||
props.stats.forEach(player => {
|
|
||||||
store.state.playersArr.forEach(p => {
|
|
||||||
if (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 => {
|
|
||||||
// 0: weapon
|
|
||||||
// 1: hitgroup
|
|
||||||
// 2: dmg
|
|
||||||
if (weapon) {
|
|
||||||
if (by === 'hitgroup') {
|
|
||||||
if (Object.values(weapon)[0] === parseInt(Object.keys(data.selectWeapon).toString())) {
|
|
||||||
let 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') {
|
|
||||||
if (Object.values(weapon)[0] === parseInt(Object.keys(data.selectWeapon).toString())) {
|
|
||||||
let 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;
|
|
||||||
|
|
||||||
default:
|
|
||||||
props.stats.forEach(player => {
|
|
||||||
if (Object.keys(player).toString() === data.selectPlayer.steamid64) {
|
|
||||||
Object.values(player).forEach(enemies => {
|
|
||||||
Object.values(enemies).forEach(weapons => {
|
|
||||||
Object.values(weapons).forEach(weapon => {
|
|
||||||
// 0: weapon
|
|
||||||
// 1: hitgroup
|
|
||||||
// 2: dmg
|
|
||||||
if (weapon) {
|
|
||||||
if (by === 'hitgroup') {
|
|
||||||
if (Object.values(weapon)[0] === parseInt(Object.keys(data.selectWeapon).toString())) {
|
|
||||||
let 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') {
|
|
||||||
if (Object.values(weapon)[0] === parseInt(Object.keys(data.selectWeapon).toString())) {
|
|
||||||
let 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;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
arr = []
|
|
||||||
}
|
|
||||||
|
|
||||||
if (by === 'hitgroup') {
|
|
||||||
buildCharts(sumDmgArr(arr))
|
|
||||||
} else if (by === 'weapon') {
|
|
||||||
data.weaponDmg = sumDmgArr(arr, 'weapon')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sumDmgArr = (arr, by = 'hitgroup') => {
|
|
||||||
let holder = {};
|
|
||||||
|
|
||||||
arr.forEach(function (d) {
|
|
||||||
// eslint-disable-next-line no-prototype-builtins
|
|
||||||
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());
|
|
||||||
} else {
|
|
||||||
holder[parseInt(Object.keys(d).toString())] = parseInt(Object.values(d).toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let arr2 = [];
|
|
||||||
|
|
||||||
if (by === 'hitgroup') {
|
|
||||||
for (let i = 1; i < 8; i++) {
|
|
||||||
if (holder[i] !== undefined) {
|
|
||||||
arr2.push(holder[i])
|
|
||||||
} else {
|
|
||||||
arr2.push(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (by === 'weapon') {
|
|
||||||
for (let i = 1; i < 312; i++) {
|
|
||||||
if (holder[i] !== undefined) {
|
|
||||||
let obj = {}
|
|
||||||
obj[i] = holder[i]
|
|
||||||
arr2.push(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
arr2.sort((a, b) => {
|
|
||||||
return Object.values(b).toString() - Object.values(a).toString()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return arr2
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMax = (arr) => {
|
|
||||||
let max = 0
|
|
||||||
for (let i = 0; i < 7; i++) {
|
|
||||||
if (arr[i] > max)
|
|
||||||
max = arr[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
|
|
||||||
const optionGen = (arr = []) => {
|
|
||||||
return {
|
|
||||||
tooltip: {},
|
|
||||||
visualMap: {
|
|
||||||
left: 'center',
|
|
||||||
bottom: '5%',
|
|
||||||
textStyle: {
|
|
||||||
color: 'white',
|
|
||||||
},
|
|
||||||
min: 0,
|
|
||||||
max: getMax(arr) || 100,
|
|
||||||
orient: 'horizontal',
|
|
||||||
realtime: true,
|
|
||||||
calculable: true,
|
|
||||||
inRange: {
|
|
||||||
color: ['#00ff00', '#db6e00', '#cf0000']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'Hitgroup',
|
|
||||||
type: 'map',
|
|
||||||
map: 'hitgroup-puppet',
|
|
||||||
top: '0%',
|
|
||||||
emphasis: {
|
|
||||||
label: {
|
|
||||||
show: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectedMode: false,
|
|
||||||
data: [
|
|
||||||
{name: 'Head', value: arr[0] || 0},
|
|
||||||
{name: 'Chest', value: arr[1] || 0},
|
|
||||||
{name: 'Stomach', value: arr[2] || 0},
|
|
||||||
{name: 'Left Arm', value: arr[3] || 0},
|
|
||||||
{name: 'Right Arm', value: arr[4] || 0},
|
|
||||||
{name: 'Left Foot', value: arr[5] || 0},
|
|
||||||
{name: 'Right Foot', value: arr[6] || 0}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const disposeCharts = () => {
|
|
||||||
if (myChart1 != null && myChart1 !== '' && myChart1 !== undefined) {
|
|
||||||
myChart1.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildCharts = (arr) => {
|
|
||||||
disposeCharts()
|
|
||||||
|
|
||||||
myChart1 = echarts.init(document.getElementById('hitgroup-puppet'), {}, {width: 300, height: 500})
|
|
||||||
|
|
||||||
const url = '/images/icons/hitgroup-puppet.svg'
|
|
||||||
$.get(url, function (svg) {
|
|
||||||
echarts.registerMap('hitgroup-puppet', {svg: svg})
|
|
||||||
myChart1.setOption(optionGen(arr));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (store.state.matchDetails.stats) {
|
|
||||||
echarts.use([
|
|
||||||
TooltipComponent,
|
|
||||||
VisualMapComponent,
|
|
||||||
GeoComponent,
|
|
||||||
MapChart,
|
|
||||||
CanvasRenderer
|
|
||||||
]);
|
|
||||||
|
|
||||||
buildCharts()
|
|
||||||
|
|
||||||
watch(() => props.stats, () => {
|
|
||||||
processDmg()
|
|
||||||
processDmg('weapon')
|
|
||||||
processPlayerWeapon()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
disposeCharts()
|
|
||||||
})
|
|
||||||
|
|
||||||
window.onresize = () => {
|
|
||||||
if (window.innerWidth <= 750) {
|
|
||||||
width.value = getWindowWidth() - 20
|
|
||||||
height.value = setHeight()
|
|
||||||
dmgWidth.value = setDmgWidth()
|
|
||||||
}
|
|
||||||
|
|
||||||
buildCharts()
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => data.selectPlayer, () => {
|
|
||||||
data.selectWeapon = 'All'
|
|
||||||
processPlayerWeapon()
|
|
||||||
processDmg()
|
|
||||||
processDmg('weapon')
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => data.selectWeapon, () => {
|
|
||||||
processDmg()
|
|
||||||
processDmg('weapon')
|
|
||||||
})
|
|
||||||
|
|
||||||
return {props, data, store, dmgWidth, processPlayerWeapon, processWeaponDmg, DisplayWeapon}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.select-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 1rem;
|
|
||||||
|
|
||||||
.form-select {
|
|
||||||
background: var(--bs-secondary);
|
|
||||||
color: var(--bs-primary);
|
|
||||||
width: 250px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.select-group {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-if="infos.data" id="modal">
|
|
||||||
<div v-for="(info, id) in infos.data" :key="id" class="custom-modal">
|
|
||||||
<div :class="info.type === 'error'
|
|
||||||
? 'bg-danger text-white'
|
|
||||||
: info.type === 'warning'
|
|
||||||
? 'bg-warning text-secondary'
|
|
||||||
: info.type === 'success'
|
|
||||||
? 'bg-success text-white'
|
|
||||||
: 'bg-secondary text-white'"
|
|
||||||
class="card">
|
|
||||||
<div class="card-body d-flex justify-content-between">
|
|
||||||
<span class="info-text">{{ info.message }}</span>
|
|
||||||
<button aria-label="Close" class="btn-close" type="button" @click="closeModal(id)"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {useStore} from "vuex";
|
|
||||||
import {onMounted, reactive} from "vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "InfoModal",
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
const infos = reactive({
|
|
||||||
data: []
|
|
||||||
})
|
|
||||||
|
|
||||||
const closeModal = (id) => {
|
|
||||||
store.commit('removeInfoState', id)
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
#modal {
|
|
||||||
--height: 56px;
|
|
||||||
|
|
||||||
.card {
|
|
||||||
z-index: 10;
|
|
||||||
position: absolute;
|
|
||||||
right: 1rem;
|
|
||||||
opacity: .8;
|
|
||||||
width: min(100vw - 2rem, 50ch);
|
|
||||||
height: var(--height);
|
|
||||||
|
|
||||||
.btn-close {
|
|
||||||
background-color: white;
|
|
||||||
opacity: .5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-text {
|
|
||||||
font-size: .8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@for $i from 1 through 10 {
|
|
||||||
.custom-modal:nth-of-type(#{$i}) {
|
|
||||||
.card {
|
|
||||||
@if $i == 1 {
|
|
||||||
margin: 1rem 0;
|
|
||||||
} @else {
|
|
||||||
margin-top: calc(#{$i}rem + (#{$i} - 1) * var(--height));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,274 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="container w-50">
|
|
||||||
<TranslateChatButton
|
|
||||||
v-if="data.chat.length > 0"
|
|
||||||
:translated="data.translatedText.length > 0"
|
|
||||||
class="translate-btn"
|
|
||||||
@translated="handleTranslatedText"
|
|
||||||
/>
|
|
||||||
<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">
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="(m, id) in data.chat" :key="id">
|
|
||||||
<td class="td-time">
|
|
||||||
{{ ConvertTickToTime(m.tick, m.tick_rate) }}
|
|
||||||
</td>
|
|
||||||
<td class="td-avatar">
|
|
||||||
<img :class="'team-color-' + m.color"
|
|
||||||
:src="constructAvatarUrl(m.avatar)"
|
|
||||||
alt="Player avatar"
|
|
||||||
class="avatar">
|
|
||||||
</td>
|
|
||||||
<td :class="m.startSide === 1 ? 'text-info' : 'text-warning'"
|
|
||||||
class="td-name d-flex"
|
|
||||||
@click="GoToPlayer(m.steamid64)">
|
|
||||||
<span>
|
|
||||||
<i v-if="m.tracked" class="fa fa-dot-circle-o text-success tracked" title="Tracked user"/>
|
|
||||||
<span :class="(m.vac && FormatVacDate(m.vac_date, store.state.matchDetails.date) !== '')
|
|
||||||
|| (!m.vac && m.game_ban && FormatVacDate(m.game_ban_date, store.state.matchDetails.date) !== '')
|
|
||||||
? 'ban-shadow'
|
|
||||||
: ''"
|
|
||||||
:title="!m.vac && m.game_ban
|
|
||||||
? 'Game-banned: ' + FormatVacDate(m.game_ban_date, store.state.matchDetails.date)
|
|
||||||
: m.vac && !m.game_ban
|
|
||||||
? 'Vac-banned: ' + FormatVacDate(m.vac_date, store.state.matchDetails.date)
|
|
||||||
: ''">
|
|
||||||
{{ m.player }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="td-icon">
|
|
||||||
<i class="fa fa-caret-right"/>
|
|
||||||
<span v-if="!m.all_chat" class="ms-1">
|
|
||||||
(team)
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="td-message">
|
|
||||||
{{ data.translatedText.length === 0 ? m.message : data.originalChat[id].message }}
|
|
||||||
<span v-if="m.translated_from"
|
|
||||||
:class="m.translated_from ? 'text-success' : ''"
|
|
||||||
:title="`Translated from ${ISO6391.getName(m.translated_from)}`"
|
|
||||||
class="ms-2 helpicon">
|
|
||||||
<br/>
|
|
||||||
{{ m.message }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<h3>No chat available</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {useStore} from "vuex";
|
|
||||||
import {onMounted, reactive} from "vue";
|
|
||||||
import {constructAvatarUrl, ConvertTickToTime, FormatVacDate, GetChatHistory, GoToPlayer, truncate} from "@/utils";
|
|
||||||
import TranslateChatButton from "@/components/TranslateChatButton";
|
|
||||||
import ISO6391 from 'iso-639-1'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "MatchChatHistory",
|
|
||||||
components: {TranslateChatButton},
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const data = reactive({
|
|
||||||
chat: [],
|
|
||||||
translatedText: [],
|
|
||||||
originalChat: [],
|
|
||||||
clientWidth: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleTranslatedText = async (e) => {
|
|
||||||
const [res, toggle] = await e
|
|
||||||
|
|
||||||
if (res !== null) {
|
|
||||||
if (toggle === 'translated') {
|
|
||||||
data.translatedText = await setPlayer(sortChatHistory(res, true))
|
|
||||||
data.chat = data.translatedText
|
|
||||||
} else if (toggle === 'original') {
|
|
||||||
data.chat = data.originalChat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.translate-btn {
|
|
||||||
margin-top: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.td-time {
|
|
||||||
width: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.td-avatar {
|
|
||||||
width: 30px;
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.td-name {
|
|
||||||
width: 200px;
|
|
||||||
max-width: 200px;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: left;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
.tracked {
|
|
||||||
font-size: .8rem;
|
|
||||||
margin-right: .2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ban-shadow {
|
|
||||||
color: red;
|
|
||||||
text-shadow: 0 0 1rem orangered;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.td-icon {
|
|
||||||
width: 20px;
|
|
||||||
|
|
||||||
.fa-caret-right {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.td-message {
|
|
||||||
width: 400px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
.container {
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
.td-name {
|
|
||||||
width: 120px !important;
|
|
||||||
max-width: 120px !important;
|
|
||||||
}
|
|
||||||
.td-message {
|
|
||||||
width: auto !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 576px) {
|
|
||||||
.container {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
.td-avatar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,532 +0,0 @@
|
|||||||
<template>
|
|
||||||
<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'"
|
|
||||||
class="placeholder col-12"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else id="matches">
|
|
||||||
<table class="table table-borderless">
|
|
||||||
<thead class="border-bottom">
|
|
||||||
<tr>
|
|
||||||
<th class="text-center map" scope="col">Map</th>
|
|
||||||
<th class="text-center rank" scope="col">Rank</th>
|
|
||||||
<th class="text-center length" scope="col" title="Match Length">
|
|
||||||
<img alt="Match length" class="match-len helpicon" src="/images/icons/timer_both.svg">
|
|
||||||
</th>
|
|
||||||
<th class="text-center score" scope="col">Score</th>
|
|
||||||
<th v-if="!props.explore" class="text-center kills" scope="col">K</th>
|
|
||||||
<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 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>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="match in props.matches"
|
|
||||||
: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' : '')"
|
|
||||||
:title="match.vac ? 'VAC-banned player in this game' : match.game_ban ? 'Game-banned player in this game' : ''"
|
|
||||||
class="match default"
|
|
||||||
@click="GoToMatch(match.match_id)"
|
|
||||||
>
|
|
||||||
<td class="td-map text-center">
|
|
||||||
<i v-if="match.parsed" class="fa fa-bar-chart parsed helpicon"
|
|
||||||
title="Demo has been parsed for additional data"></i>
|
|
||||||
<i v-if="!match.parsed && MatchNotParsedTime(match.date)" class="fa fa-hourglass-half not-yet-parsed helpicon"
|
|
||||||
title="Match has not been parsed yet"></i>
|
|
||||||
<img v-if="match.map !== ''"
|
|
||||||
:alt="match.map"
|
|
||||||
:src="'/images/map_icons/map_icon_' + match.map + '.svg'"
|
|
||||||
:title="FixMapName(match.map)"
|
|
||||||
class="map-icon">
|
|
||||||
<i v-else class="fa fa-question-circle-o map-not-found" title="Match not parsed"></i>
|
|
||||||
</td>
|
|
||||||
<td class="td-rank text-center">
|
|
||||||
<img v-if="props.explore"
|
|
||||||
:alt="DisplayRank(Math.floor(match.avg_rank || 0))[1]"
|
|
||||||
:src="DisplayRank(Math.floor(match.avg_rank || 0))[0]"
|
|
||||||
:title="DisplayRank(Math.floor(match.avg_rank || 0))[1]" class="rank-icon">
|
|
||||||
<img v-else
|
|
||||||
:alt="DisplayRank(match.stats.rank?.new)[1]"
|
|
||||||
:class="match.stats.rank?.new > match.stats.rank?.old ? 'uprank' : match.stats.rank?.new < match.stats.rank?.old ? 'downrank' : ''"
|
|
||||||
:src="DisplayRank(match.stats.rank?.new)[0]"
|
|
||||||
:title="DisplayRank(match.stats.rank?.new)[1]" class="rank-icon">
|
|
||||||
</td>
|
|
||||||
<td class="td-length text-center">
|
|
||||||
<img v-if="match.max_rounds === 30 || !match.max_rounds"
|
|
||||||
alt="Match long"
|
|
||||||
class="match-len"
|
|
||||||
src="/images/icons/timer_long.svg"
|
|
||||||
title="Long Match">
|
|
||||||
<img v-if="match.max_rounds === 16"
|
|
||||||
alt="Match short"
|
|
||||||
class="match-len"
|
|
||||||
src="/images/icons/timer_short.svg"
|
|
||||||
title="Short Match">
|
|
||||||
</td>
|
|
||||||
<td class="td-score text-center fw-bold">
|
|
||||||
<span
|
|
||||||
:class="match.match_result === 1 ? 'text-success' : match.match_result === 0 ? 'text-warning' : 'text-danger'">{{
|
|
||||||
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.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) >= 1 ? 'text-success' : 'text-warning'"
|
|
||||||
class="td-hltv text-center fw-bold">
|
|
||||||
{{
|
|
||||||
GetHLTV_1(
|
|
||||||
match.stats.kills,
|
|
||||||
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>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {
|
|
||||||
DisplayRank,
|
|
||||||
FixMapName,
|
|
||||||
FormatDate,
|
|
||||||
FormatDuration,
|
|
||||||
FormatFullDate,
|
|
||||||
FormatFullDuration,
|
|
||||||
GetHLTV_1,
|
|
||||||
GetWinLoss,
|
|
||||||
GoToMatch,
|
|
||||||
MatchNotParsedTime
|
|
||||||
} from "@/utils";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "MatchesTable",
|
|
||||||
props: {
|
|
||||||
colorFront: {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
|
|
||||||
#matches-placeholder {
|
|
||||||
.placeholder {
|
|
||||||
height: 78px;
|
|
||||||
margin: 1px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
margin-bottom: 0;
|
|
||||||
|
|
||||||
tr {
|
|
||||||
th {
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
line-height: 60px;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
th:last-child, td:last-child {
|
|
||||||
text-align: right;
|
|
||||||
width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.map {
|
|
||||||
padding-left: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.match-len {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.td-map {
|
|
||||||
position: relative;
|
|
||||||
padding-left: 3rem;
|
|
||||||
text-align: left !important;
|
|
||||||
width: 50px;
|
|
||||||
|
|
||||||
.parsed {
|
|
||||||
position: absolute;
|
|
||||||
left: 7px;
|
|
||||||
bottom: 23px;
|
|
||||||
color: var(--bs-warning);
|
|
||||||
font-size: 1.7rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.not-yet-parsed {
|
|
||||||
position: absolute;
|
|
||||||
left: 10px;
|
|
||||||
bottom: 25px;
|
|
||||||
color: darkgrey;
|
|
||||||
font-size: 1.7rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-not-found {
|
|
||||||
position: absolute;
|
|
||||||
top: 4px;
|
|
||||||
left: 48px;
|
|
||||||
font-size: 4.35rem;
|
|
||||||
color: rgba(255, 193, 7, .86);
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 60px;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.td-rank {
|
|
||||||
img {
|
|
||||||
width: 70px;
|
|
||||||
height: auto;
|
|
||||||
|
|
||||||
.rank-icon {
|
|
||||||
height: 35px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.td-score {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.td-date, .date {
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.match {
|
|
||||||
$first: rgb(0, 0, 0);
|
|
||||||
$last: rgb(0, 0, 0);
|
|
||||||
$win: false;
|
|
||||||
$loss: false;
|
|
||||||
$draw: false;
|
|
||||||
$ban: false;
|
|
||||||
|
|
||||||
&.default {
|
|
||||||
background: linear-gradient(to right,
|
|
||||||
rgba($first, 0.2) 0%,
|
|
||||||
rgba($first, 0.1) 15%,
|
|
||||||
rgba(0, 0, 0, 0.4) 30%,
|
|
||||||
rgba(0, 0, 0, 0.4) 70%,
|
|
||||||
rgba($last, 0.6) 80%,
|
|
||||||
rgba($last, 0.6) 100%
|
|
||||||
);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: linear-gradient(to right,
|
|
||||||
rgba($first, 0.3) 0%,
|
|
||||||
rgba($first, 0.2) 15%,
|
|
||||||
rgba(0, 0, 0, 0.5) 30%,
|
|
||||||
rgba(0, 0, 0, 0.5) 70%,
|
|
||||||
rgba($last, 0.7) 80%,
|
|
||||||
rgba($last, 0.7) 100%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.win {
|
|
||||||
$first: rgb(0, 255, 0);
|
|
||||||
background: linear-gradient(to right,
|
|
||||||
rgba($first, 0.2) 0%,
|
|
||||||
rgba($first, 0.1) 15%,
|
|
||||||
rgba(0, 0, 0, 0.4) 30%,
|
|
||||||
rgba(0, 0, 0, 0.4) 70%,
|
|
||||||
rgba($last, 0.6) 80%,
|
|
||||||
rgba($last, 0.6) 100%
|
|
||||||
);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: linear-gradient(to right,
|
|
||||||
rgba($first, 0.3) 0%,
|
|
||||||
rgba($first, 0.2) 15%,
|
|
||||||
rgba(0, 0, 0, 0.5) 30%,
|
|
||||||
rgba(0, 0, 0, 0.5) 70%,
|
|
||||||
rgba($last, 0.7) 80%,
|
|
||||||
rgba($last, 0.7) 100%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.draw {
|
|
||||||
$first: rgb(255, 255, 0);
|
|
||||||
background: linear-gradient(to right,
|
|
||||||
rgba($first, 0.2) 0%,
|
|
||||||
rgba($first, 0.1) 15%,
|
|
||||||
rgba(0, 0, 0, 0.4) 30%,
|
|
||||||
rgba(0, 0, 0, 0.4) 70%,
|
|
||||||
rgba($last, 0.6) 80%,
|
|
||||||
rgba($last, 0.6) 100%
|
|
||||||
);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: linear-gradient(to right,
|
|
||||||
rgba($first, 0.3) 0%,
|
|
||||||
rgba($first, 0.2) 15%,
|
|
||||||
rgba(0, 0, 0, 0.5) 30%,
|
|
||||||
rgba(0, 0, 0, 0.5) 70%,
|
|
||||||
rgba($last, 0.7) 80%,
|
|
||||||
rgba($last, 0.7) 100%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.loss {
|
|
||||||
$first: rgb(255, 0, 0);
|
|
||||||
background: linear-gradient(to right,
|
|
||||||
rgba($first, 0.2) 0%,
|
|
||||||
rgba($first, 0.1) 15%,
|
|
||||||
rgba(0, 0, 0, 0.4) 30%,
|
|
||||||
rgba(0, 0, 0, 0.4) 70%,
|
|
||||||
rgba($last, 0.6) 80%,
|
|
||||||
rgba($last, 0.6) 100%
|
|
||||||
);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: linear-gradient(to right,
|
|
||||||
rgba($first, 0.3) 0%,
|
|
||||||
rgba($first, 0.2) 15%,
|
|
||||||
rgba(0, 0, 0, 0.5) 30%,
|
|
||||||
rgba(0, 0, 0, 0.5) 70%,
|
|
||||||
rgba($last, 0.7) 80%,
|
|
||||||
rgba($last, 0.7) 100%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ban {
|
|
||||||
$last: rgb(93, 3, 3);
|
|
||||||
background: linear-gradient(to right,
|
|
||||||
rgba($first, 0.2) 0%,
|
|
||||||
rgba($first, 0.1) 15%,
|
|
||||||
rgba(0, 0, 0, 0.4) 30%,
|
|
||||||
rgba(0, 0, 0, 0.4) 70%,
|
|
||||||
rgba($last, 0.6) 80%,
|
|
||||||
rgba($last, 0.6) 100%
|
|
||||||
);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: linear-gradient(to right,
|
|
||||||
rgba($first, 0.3) 0%,
|
|
||||||
rgba($first, 0.2) 15%,
|
|
||||||
rgba(0, 0, 0, 0.5) 30%,
|
|
||||||
rgba(0, 0, 0, 0.5) 70%,
|
|
||||||
rgba($last, 0.7) 80%,
|
|
||||||
rgba($last, 0.7) 100%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.matches_ban {
|
|
||||||
$first: rgb(0, 0, 0);
|
|
||||||
$last: rgb(93, 3, 3);
|
|
||||||
background: linear-gradient(to right,
|
|
||||||
rgba($first, 0.2) 0%,
|
|
||||||
rgba($first, 0.1) 15%,
|
|
||||||
rgba(0, 0, 0, 0.4) 30%,
|
|
||||||
rgba(0, 0, 0, 0.4) 70%,
|
|
||||||
rgba($last, 0.6) 80%,
|
|
||||||
rgba($last, 0.6) 100%
|
|
||||||
);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: linear-gradient(to right,
|
|
||||||
rgba($first, 0.3) 0%,
|
|
||||||
rgba($first, 0.2) 15%,
|
|
||||||
rgba(0, 0, 0, 0.5) 30%,
|
|
||||||
rgba(0, 0, 0, 0.5) 70%,
|
|
||||||
rgba($last, 0.7) 80%,
|
|
||||||
rgba($last, 0.7) 100%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
border-bottom: 1px solid rgba(73, 73, 73, 0.73);
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 400px) {
|
|
||||||
table tr {
|
|
||||||
.map-icon {
|
|
||||||
margin-left: 0 !important;
|
|
||||||
}
|
|
||||||
.map {
|
|
||||||
padding: 0.5rem !important;
|
|
||||||
}
|
|
||||||
.td-map {
|
|
||||||
padding: 0 1rem !important;
|
|
||||||
|
|
||||||
.parsed {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.not-yet-parsed {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
.map-icon {
|
|
||||||
margin-left: -1.32em !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.td-map {
|
|
||||||
position: relative;
|
|
||||||
width: 35px !important;
|
|
||||||
|
|
||||||
.parsed {
|
|
||||||
position: absolute;
|
|
||||||
left: .3rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.not-yet-parsed {
|
|
||||||
position: absolute;
|
|
||||||
left: .3rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 35px !important;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.td-rank img {
|
|
||||||
width: 50px !important;
|
|
||||||
height: auto;
|
|
||||||
max-width: 50px !important;
|
|
||||||
margin-left: -0.5rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.td-score {
|
|
||||||
font-size: .7rem !important;
|
|
||||||
//width: 110px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.td-date {
|
|
||||||
font-size: .8rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kills, .deaths, .assists, .kdiff, .duration, .hltv, .length,
|
|
||||||
.td-kills, .td-deaths, .td-assists, .td-plus, .td-duration, .td-hltv, .td-length {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 992px) {
|
|
||||||
.avatar {
|
|
||||||
width: 100px !important;
|
|
||||||
height: 100px !important;
|
|
||||||
}
|
|
||||||
.trackme-btn {
|
|
||||||
top: 25px;
|
|
||||||
}
|
|
||||||
.map, .td-map {
|
|
||||||
padding-left: 4rem !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 1200px) {
|
|
||||||
.td-plus, .kdiff {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.td-rank img {
|
|
||||||
width: 60px !important;
|
|
||||||
height: auto;
|
|
||||||
max-width: 60px;
|
|
||||||
}
|
|
||||||
.td-map img {
|
|
||||||
width: 50px !important;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
.td-score {
|
|
||||||
font-size: 1.1rem !important;
|
|
||||||
width: 130px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="charts">
|
|
||||||
<div id="multi-kills-chart-1"></div>
|
|
||||||
<div id="multi-kills-chart-2"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import * as echarts from 'echarts/core';
|
|
||||||
import {GridComponent, TooltipComponent, VisualMapComponent} from 'echarts/components';
|
|
||||||
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 {
|
|
||||||
name: "MultiKillsChart",
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const multiKills = ['2k', '3k', '4k', '5k']
|
|
||||||
let myChart1, myChart2
|
|
||||||
const width = ref(window.innerWidth <= 500 ? window.innerWidth : 500)
|
|
||||||
const height = ref(width.value)
|
|
||||||
|
|
||||||
const multiKillArr = (stats, team) => {
|
|
||||||
let arr = []
|
|
||||||
for (let i = (team - 1) * 5; i < team * 5; i++) {
|
|
||||||
for (let j = 0; j < multiKills.length; j++) {
|
|
||||||
if (j === 0)
|
|
||||||
arr.push([i % 5, j, checkStatEmpty(stats[i].multi_kills.duo) === 0 ? null : stats[i].multi_kills.duo])
|
|
||||||
if (j === 1)
|
|
||||||
arr.push([i % 5, j, checkStatEmpty(stats[i].multi_kills.triple) === 0 ? null : stats[i].multi_kills.triple])
|
|
||||||
if (j === 2)
|
|
||||||
arr.push([i % 5, j, checkStatEmpty(stats[i].multi_kills.quad) === 0 ? null : stats[i].multi_kills.quad])
|
|
||||||
if (j === 3)
|
|
||||||
arr.push([i % 5, j, checkStatEmpty(stats[i].multi_kills.pent) === 0 ? null : stats[i].multi_kills.pent])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMax = (stats, team) => {
|
|
||||||
let max = 0
|
|
||||||
for (let i = (team - 1) * 5; i < team * 5; i++) {
|
|
||||||
if (stats[i].multi_kills.duo > max)
|
|
||||||
max = stats[i].multi_kills.duo
|
|
||||||
if (stats[i].multi_kills.triple > max)
|
|
||||||
max = stats[i].multi_kills.triple
|
|
||||||
if (stats[i].multi_kills.quad > max)
|
|
||||||
max = stats[i].multi_kills.quad
|
|
||||||
if (stats[i].multi_kills.pent > max)
|
|
||||||
max = stats[i].multi_kills.pent
|
|
||||||
}
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
|
|
||||||
const optionGen = (team) => {
|
|
||||||
return {
|
|
||||||
tooltip: {},
|
|
||||||
grid: {
|
|
||||||
height: '65%',
|
|
||||||
top: '0%',
|
|
||||||
bottom: '10%'
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
data: getPlayerArr(store.state.matchDetails.stats, team, true).reverse(),
|
|
||||||
splitArea: {
|
|
||||||
show: true
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
fontSize: 14,
|
|
||||||
color: 'white',
|
|
||||||
rotate: 50
|
|
||||||
}
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'category',
|
|
||||||
data: multiKills,
|
|
||||||
splitArea: {
|
|
||||||
show: true
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
color: 'white'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
visualMap: {
|
|
||||||
min: 0,
|
|
||||||
max: getMax(store.state.matchDetails.stats, team),
|
|
||||||
calculable: true,
|
|
||||||
orient: 'horizontal',
|
|
||||||
left: 'center',
|
|
||||||
bottom: '5%',
|
|
||||||
textStyle: {
|
|
||||||
color: 'white'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
type: 'heatmap',
|
|
||||||
data: multiKillArr(store.state.matchDetails.stats, team),
|
|
||||||
label: {
|
|
||||||
fontSize: 14,
|
|
||||||
show: true
|
|
||||||
},
|
|
||||||
emphasis: {
|
|
||||||
itemStyle: {
|
|
||||||
shadowBlur: 10,
|
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const disposeCharts = () => {
|
|
||||||
if (myChart1 != null && myChart1 !== '' && myChart1 !== undefined) {
|
|
||||||
myChart1.dispose()
|
|
||||||
}
|
|
||||||
if (myChart2 != null && myChart2 !== '' && myChart2 !== undefined) {
|
|
||||||
myChart2.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildCharts = () => {
|
|
||||||
disposeCharts()
|
|
||||||
|
|
||||||
myChart1 = echarts.init(document.getElementById('multi-kills-chart-1'), {}, {
|
|
||||||
width: width.value,
|
|
||||||
height: height.value
|
|
||||||
});
|
|
||||||
myChart1.setOption(optionGen(1));
|
|
||||||
|
|
||||||
myChart2 = echarts.init(document.getElementById('multi-kills-chart-2'), {}, {
|
|
||||||
width: width.value,
|
|
||||||
height: height.value
|
|
||||||
});
|
|
||||||
myChart2.setOption(optionGen(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (store.state.matchDetails.stats) {
|
|
||||||
echarts.use([
|
|
||||||
TooltipComponent,
|
|
||||||
GridComponent,
|
|
||||||
VisualMapComponent,
|
|
||||||
HeatmapChart,
|
|
||||||
CanvasRenderer
|
|
||||||
]);
|
|
||||||
|
|
||||||
buildCharts()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
disposeCharts()
|
|
||||||
})
|
|
||||||
|
|
||||||
window.onresize = () => {
|
|
||||||
if (window.innerWidth <= 500) {
|
|
||||||
width.value = window.innerWidth - 20
|
|
||||||
height.value = width.value
|
|
||||||
|
|
||||||
buildCharts()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.charts {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
#multi-kills-chart-1,
|
|
||||||
#multi-kills-chart-2 {
|
|
||||||
flex-basis: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,373 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="side-info">
|
|
||||||
|
|
||||||
<div v-if="props.player_meta.most_mates" class="side-info-box most-played-with">
|
|
||||||
<div class="heading">
|
|
||||||
<h5>Most played with</h5>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<ul 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">
|
|
||||||
<img :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>
|
|
||||||
<span class="end">
|
|
||||||
{{ mate.total }}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="mostMatesLoading" class="side-info-box most-played-with">
|
|
||||||
<div class="heading">
|
|
||||||
<h5>Most played with</h5>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<ul class="list-unstyled placeholder-glow">
|
|
||||||
<li class="placeholder col-11"></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="props.player_meta.best_mates" class="side-info-box best-mate">
|
|
||||||
<div class="heading">
|
|
||||||
<h5>Best Mate <span class="text-muted">(by winrate)</span></h5>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<ul 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">
|
|
||||||
<img :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>
|
|
||||||
<span class="end">
|
|
||||||
{{ mate.win_rate ? (mate.win_rate * 100).toFixed(0) : 0 }} %
|
|
||||||
<span v-if="mate.total" class="total text-muted">({{ mate.total }})</span>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="bestMatesLoading" class="side-info-box best-mate">
|
|
||||||
<div class="heading">
|
|
||||||
<h5>Best Mate <span class="text-muted">(by winrate)</span></h5>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<ul class="list-unstyled placeholder-glow">
|
|
||||||
<li class="placeholder col-11"></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="props.player_meta.eq_map && props.player_meta.weapon_dmg" class="side-info-box preferred-weapons">
|
|
||||||
<div class="heading">
|
|
||||||
<h5>Weapons <span class="text-muted">(by dmg)</span></h5>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<ul v-for="(id, key) in data.best_weapons" :key="id[0]" class="list-unstyled">
|
|
||||||
<li>
|
|
||||||
<span class="start">
|
|
||||||
<span class="text">{{ id[0] }}</span>
|
|
||||||
</span>
|
|
||||||
<span :title="id[0] + ' - ' + id[1] + ' dmg'" class="end">
|
|
||||||
<span :class="'dmg-chart-' + key">
|
|
||||||
{{ id[1] }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
{{ setDmgGraphWidth() }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="weaponsLoading" class="side-info-box preferred-weapons">
|
|
||||||
<div class="heading">
|
|
||||||
<h5>Weapons <span class="text-muted">(by dmg)</span></h5>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<ul class="list-unstyled placeholder-glow">
|
|
||||||
<li class="placeholder col-11"></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="props.player_meta.win_maps" class="side-info-box best-map">
|
|
||||||
<div class="heading">
|
|
||||||
<h5>Best Map <span class="text-muted">(by winrate)</span></h5>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<ul v-for="map in data.best_maps" :key="map[0]" class="list-unstyled">
|
|
||||||
<li>
|
|
||||||
<span class="start">
|
|
||||||
<img :src="'/images/map_icons/map_icon_' + map[0] + '.svg'" alt="Player avatar">
|
|
||||||
<span class="text">{{ FixMapName(map[0]) }}</span>
|
|
||||||
</span>
|
|
||||||
<span class="end">
|
|
||||||
{{ (map[1] * 100).toFixed(0) }} %
|
|
||||||
<span v-if="props.player_meta.total_maps[map[0]]"
|
|
||||||
class="total text-muted">({{ props.player_meta.total_maps[map[0]] }})</span>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="mapsLoading" class="side-info-box best-map">
|
|
||||||
<div class="heading">
|
|
||||||
<h5>Best Map <span class="text-muted">(by winrate)</span></h5>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<ul class="list-unstyled placeholder-glow">
|
|
||||||
<li class="placeholder col-11"></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import {constructAvatarUrl, FixMapName, GoToPlayer, sortObjectValue} from "@/utils";
|
|
||||||
import {reactive, ref, watch} from "vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "PlayerSideInfo",
|
|
||||||
props: {
|
|
||||||
player_meta: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const displayCounter = 3
|
|
||||||
|
|
||||||
const mostMatesLoading = ref(true)
|
|
||||||
const bestMatesLoading = ref(true)
|
|
||||||
const weaponsLoading = ref(true)
|
|
||||||
const mapsLoading = ref(true)
|
|
||||||
|
|
||||||
const data = reactive({
|
|
||||||
best_maps: [],
|
|
||||||
best_weapons_tmp: [],
|
|
||||||
best_weapons: []
|
|
||||||
})
|
|
||||||
|
|
||||||
const mapWeaponDamage = () => {
|
|
||||||
if (props.player_meta.eq_map && props.player_meta.weapon_dmg) {
|
|
||||||
Object.keys(props.player_meta.eq_map).forEach((key) => {
|
|
||||||
for (const id in props.player_meta.weapon_dmg) {
|
|
||||||
Object.keys(props.player_meta.weapon_dmg[id]).forEach((k) => {
|
|
||||||
if (k === 'eq') {
|
|
||||||
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.sort((a, b) => {
|
|
||||||
return b[1] - a[1]
|
|
||||||
})
|
|
||||||
|
|
||||||
data.best_weapons = data.best_weapons_tmp
|
|
||||||
data.best_weapons_tmp = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const setDmgGraphWidth = () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
let weaponsContainer
|
|
||||||
const dmg100 = ref(0)
|
|
||||||
const dmg = ref(0)
|
|
||||||
|
|
||||||
for (let i = 0; i <= 4; i++) {
|
|
||||||
weaponsContainer = document.querySelector('.dmg-chart-' + i)
|
|
||||||
if (weaponsContainer !== null) {
|
|
||||||
if (i === 0) {
|
|
||||||
dmg100.value = weaponsContainer.innerHTML * 1
|
|
||||||
weaponsContainer.style.width = '100%'
|
|
||||||
}
|
|
||||||
|
|
||||||
dmg.value = weaponsContainer.innerHTML * 1
|
|
||||||
weaponsContainer.style.width = dmg.value * 100 / dmg100.value + '%'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => props.player_meta, () => {
|
|
||||||
mapWeaponDamage()
|
|
||||||
|
|
||||||
data.best_maps = sortObjectValue(props.player_meta.win_maps, 'desc')
|
|
||||||
|
|
||||||
if (data.best_maps.length > displayCounter)
|
|
||||||
data.best_maps.splice(displayCounter, data.best_maps.length - displayCounter)
|
|
||||||
|
|
||||||
if (!props.player_meta.most_mates) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
props,
|
|
||||||
data,
|
|
||||||
weaponsLoading,
|
|
||||||
mapsLoading,
|
|
||||||
mostMatesLoading,
|
|
||||||
bestMatesLoading,
|
|
||||||
setDmgGraphWidth,
|
|
||||||
GoToPlayer,
|
|
||||||
constructAvatarUrl,
|
|
||||||
FixMapName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.side-info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
margin-top: 30px;
|
|
||||||
|
|
||||||
.placeholder {
|
|
||||||
height: 25px;
|
|
||||||
padding: 0 10px !important;
|
|
||||||
margin: 14px auto !important;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-info-box {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
background: rgba(20, 20, 20, .8);
|
|
||||||
border: 1px solid rgba(white, .3);
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol, ul, dl {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.best-mate,
|
|
||||||
.preferred-weapons,
|
|
||||||
.most-played-with,
|
|
||||||
.best-map {
|
|
||||||
.heading {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
height: 30px;
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
font-size: 1rem;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
margin: 0 0 5px 0;
|
|
||||||
border-color: rgba(white, .3);
|
|
||||||
}
|
|
||||||
|
|
||||||
ul li {
|
|
||||||
line-height: 25px;
|
|
||||||
font-size: .9rem;
|
|
||||||
padding: 0 10px;
|
|
||||||
margin: 10px 0;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
|
|
||||||
.start {
|
|
||||||
width: 50%;
|
|
||||||
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
.tracked {
|
|
||||||
font-size: .8rem;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 25px;
|
|
||||||
height: 25px;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-right: 5px;
|
|
||||||
margin-left: 5px;
|
|
||||||
|
|
||||||
&.tracked {
|
|
||||||
border: 2px solid var(--bs-success);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.end {
|
|
||||||
display: flex;
|
|
||||||
width: 45%;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.best-map, .best-mate {
|
|
||||||
ul li {
|
|
||||||
.start {
|
|
||||||
width: 75%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.end {
|
|
||||||
.total {
|
|
||||||
padding-left: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferred-weapons,
|
|
||||||
.best-map {
|
|
||||||
ul li {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferred-weapons {
|
|
||||||
.end {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
@for $i from 0 through 3 {
|
|
||||||
.dmg-chart-#{$i} {
|
|
||||||
position: absolute;
|
|
||||||
background: rgba(150, 50, 50, 1);
|
|
||||||
border-radius: 15px;
|
|
||||||
color: transparent;
|
|
||||||
user-select: none;
|
|
||||||
cursor: help;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(220, 50, 50, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,311 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="scoreboard">
|
|
||||||
<table>
|
|
||||||
<caption>
|
|
||||||
<div v-if="store.state.matchDetails.max_rounds === 16" id="short-match">
|
|
||||||
<div class="team-1">
|
|
||||||
<div class="score-text">
|
|
||||||
<span v-if="store.state.matchDetails.score[0] < 10"
|
|
||||||
:style="store.state.matchDetails.score[0] < 10 ? 'margin-left: -10px;' : ''"
|
|
||||||
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>
|
|
||||||
<img alt="CT logo" src="/images/icons/ct_logo.svg">
|
|
||||||
<img alt="T logo" src="/images/icons/t_logo.svg">
|
|
||||||
</div>
|
|
||||||
<div class="team-2">
|
|
||||||
<div class="score-text">
|
|
||||||
<span v-if="store.state.matchDetails.score[1] < 10"
|
|
||||||
:style="store.state.matchDetails.score[1] < 10 ? 'margin-left: -10px;' : ''"
|
|
||||||
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>
|
|
||||||
<img alt="T logo" src="/images/icons/t_logo.svg">
|
|
||||||
<img alt="CT logo" src="/images/icons/ct_logo.svg">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="store.state.matchDetails.max_rounds === 30 || !store.state.matchDetails.max_rounds" id="long-match">
|
|
||||||
<div class="team-1">
|
|
||||||
<div class="score-text">
|
|
||||||
<span v-if="store.state.matchDetails.score[0] < 10"
|
|
||||||
:style="store.state.matchDetails.score[0] < 10 ? 'margin-left: -10px;' : ''"
|
|
||||||
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>
|
|
||||||
<img alt="CT logo" src="/images/icons/ct_logo.svg">
|
|
||||||
<img alt="T logo" src="/images/icons/t_logo.svg">
|
|
||||||
</div>
|
|
||||||
<div class="team-2">
|
|
||||||
<div class="score-text">
|
|
||||||
<span v-if="store.state.matchDetails.score[1] < 10"
|
|
||||||
:style="store.state.matchDetails.score[1] < 10 ? 'margin-left: -10px;' : ''"
|
|
||||||
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>
|
|
||||||
<img alt="T logo" src="/images/icons/t_logo.svg">
|
|
||||||
<img alt="CT logo" src="/images/icons/ct_logo.svg">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</caption>
|
|
||||||
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="player__vac"></th>
|
|
||||||
<th class="player__avatar"></th>
|
|
||||||
<th class="player__name"></th>
|
|
||||||
<th class="player__rank"></th>
|
|
||||||
<th class="player__kills">K</th>
|
|
||||||
<th class="player__assist">A</th>
|
|
||||||
<th class="player__deaths">D</th>
|
|
||||||
<th class="player__diff helptext" title="Kill death difference">+/-</th>
|
|
||||||
<th class="player__kd">K/D</th>
|
|
||||||
<th v-if="store.state.matchDetails.parsed" class="player__adr helptext" title="Average damage per round">
|
|
||||||
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>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="player in teamStats(1)"
|
|
||||||
:key="player.player.steamid64"
|
|
||||||
class="team-1">
|
|
||||||
<ScoreTeamPlayer :assists="player.assists"
|
|
||||||
:avatar="player.player.avatar"
|
|
||||||
:color="player.color"
|
|
||||||
:deaths="player.deaths"
|
|
||||||
:dmg="player.dmg?.enemy"
|
|
||||||
:game_ban="player.player.game_ban"
|
|
||||||
:game_ban_date="player.player.game_ban_date"
|
|
||||||
:hs="player.headshot"
|
|
||||||
:kdiff="player.kills - player.deaths"
|
|
||||||
:kills="player.kills"
|
|
||||||
:mk_duo="player.multi_kills?.duo"
|
|
||||||
:mk_pent="player.multi_kills?.pent"
|
|
||||||
:mk_quad="player.multi_kills?.quad"
|
|
||||||
:mk_triple="player.multi_kills?.triple"
|
|
||||||
:mvp="player.mvp"
|
|
||||||
:name="player.player.name"
|
|
||||||
:parsed="store.state.matchDetails.parsed"
|
|
||||||
:player_score="player.score"
|
|
||||||
:rank_new="player.rank?.new"
|
|
||||||
:rank_old="player.rank?.old"
|
|
||||||
:rounds_played="store.state.matchDetails.score.reduce((a, b) => a + b)"
|
|
||||||
:steamid64="player.player.steamid64"
|
|
||||||
:tracked="player.player.tracked"
|
|
||||||
:vac="player.player.vac"
|
|
||||||
:vac_date="player.player.vac_date"
|
|
||||||
/>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr class="hr_outer">
|
|
||||||
<td colspan="14"></td>
|
|
||||||
</tr>
|
|
||||||
<tr class="hr">
|
|
||||||
<td colspan="14"></td>
|
|
||||||
</tr>
|
|
||||||
<tr class="hr_outer">
|
|
||||||
<td colspan="14"></td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr v-for="player in teamStats(2)"
|
|
||||||
:key="player.player.steamid64"
|
|
||||||
class="team-2">
|
|
||||||
<ScoreTeamPlayer :assists="player.assists"
|
|
||||||
:avatar="player.player.avatar"
|
|
||||||
:color="player.color"
|
|
||||||
:deaths="player.deaths"
|
|
||||||
:dmg="player.dmg?.enemy"
|
|
||||||
:game_ban="player.player.game_ban"
|
|
||||||
:game_ban_date="player.player.game_ban_date"
|
|
||||||
:hs="player.headshot"
|
|
||||||
:kdiff="player.kills - player.deaths"
|
|
||||||
:kills="player.kills"
|
|
||||||
:mk_duo="player.multi_kills?.duo"
|
|
||||||
:mk_pent="player.multi_kills?.pent"
|
|
||||||
:mk_quad="player.multi_kills?.quad"
|
|
||||||
:mk_triple="player.multi_kills?.triple"
|
|
||||||
:mvp="player.mvp"
|
|
||||||
:name="player.player.name"
|
|
||||||
:parsed="store.state.matchDetails.parsed"
|
|
||||||
:player_score="player.score"
|
|
||||||
:rank_new="player.rank?.new"
|
|
||||||
:rank_old="player.rank?.old"
|
|
||||||
:rounds_played="store.state.matchDetails.score.reduce((a, b) => a + b)"
|
|
||||||
:steamid64="player.player.steamid64"
|
|
||||||
:tracked="player.player.tracked"
|
|
||||||
:vac="player.player.vac"
|
|
||||||
:vac_date="player.player.vac_date"
|
|
||||||
/>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import ScoreTeamPlayer from '@/components/ScoreTeamPlayer.vue'
|
|
||||||
import {useStore} from "vuex";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ScoreTeam',
|
|
||||||
components: {ScoreTeamPlayer},
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const teamStats = (team) => {
|
|
||||||
let arr = []
|
|
||||||
|
|
||||||
if (team === 1) {
|
|
||||||
arr = []
|
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
arr.push(store.state.matchDetails.stats[i])
|
|
||||||
}
|
|
||||||
} else if (team === 2) {
|
|
||||||
arr = []
|
|
||||||
for (let i = 5; i < store.state.matchDetails.stats.length; i++) {
|
|
||||||
arr.push(store.state.matchDetails.stats[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
|
|
||||||
return {store, teamStats}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.scoreboard {
|
|
||||||
margin: 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
width: 900px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
width: 900px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
caption {
|
|
||||||
position: relative;
|
|
||||||
color: white;
|
|
||||||
caption-side: top;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
z-index: 0;
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
color: transparent;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-text {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.team-1,
|
|
||||||
.team-2 {
|
|
||||||
position: absolute;
|
|
||||||
font-size: 3rem;
|
|
||||||
opacity: .8;
|
|
||||||
|
|
||||||
margin-left: -100px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
position: absolute;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
margin-top: 22px;
|
|
||||||
margin-left: 10px;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-left: 30px;
|
|
||||||
z-index: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.team-1 {
|
|
||||||
top: 85px;
|
|
||||||
|
|
||||||
.score-text {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.team-2 {
|
|
||||||
top: 180px;
|
|
||||||
|
|
||||||
.score-text {
|
|
||||||
top: 150px;
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr.team-1, tr.team-2 {
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: 5px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hr {
|
|
||||||
td {
|
|
||||||
height: 1px;
|
|
||||||
padding: 0;
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hr_outer {
|
|
||||||
height: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player__vac {
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
.scoreboard {
|
|
||||||
margin-left: 65px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 991px) {
|
|
||||||
.scoreboard {
|
|
||||||
margin-left: 2px;
|
|
||||||
|
|
||||||
caption {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,268 +0,0 @@
|
|||||||
<template>
|
|
||||||
<td class="player__vac">
|
|
||||||
<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) !== ''"
|
|
||||||
:title="'Vac-banned: ' + 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>
|
|
||||||
<img :class="'team-color-' + props.color" :src="constructAvatarUrl(props.avatar)" alt="Player avatar"
|
|
||||||
class="player__avatar">
|
|
||||||
</td>
|
|
||||||
<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>
|
|
||||||
{{ props.name }}
|
|
||||||
<i class="fa fa-external-link"></i>
|
|
||||||
</td>
|
|
||||||
<td v-if="props.parsed" class="player__rank">
|
|
||||||
<img :alt="DisplayRank(props.rank_old)[1]"
|
|
||||||
:class="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 v-if="!props.parsed" class="rank-placeholder"></td>
|
|
||||||
<td class="player__kills">
|
|
||||||
{{ props.kills }}
|
|
||||||
</td>
|
|
||||||
<td class="player__assist">
|
|
||||||
{{ props.assists }}
|
|
||||||
</td>
|
|
||||||
<td class="player__deaths">
|
|
||||||
{{ props.deaths }}
|
|
||||||
</td>
|
|
||||||
<td :class="props.kdiff >= 0 ? 'text-success' : 'text-danger'" class="player__kdiff">
|
|
||||||
{{ props.kdiff }}
|
|
||||||
</td>
|
|
||||||
<td class="player__kd">
|
|
||||||
{{
|
|
||||||
(props.kills > 0 && props.deaths > 0) ? (props.kills / props.deaths).toFixed(2) : (props.kills > 0 && props.deaths === 0) ? props.kills : 0.00
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
<td v-if="props.parsed" class="player__adr">
|
|
||||||
{{ (props.dmg / props.rounds_played).toFixed(2) }}
|
|
||||||
</td>
|
|
||||||
<td class="player__hs">
|
|
||||||
{{ (props.hs > 0 && props.kills > 0) ? (props.hs * 100 / props.kills).toFixed(0) + "%" : "0%" }}
|
|
||||||
</td>
|
|
||||||
<td class="player__rating">
|
|
||||||
{{
|
|
||||||
GetHLTV_1(props.kills, props.rounds_played, props.deaths, props.mk_duo, props.mk_triple, props.mk_quad, props.mk_pent)
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
<td class="player__mvp">
|
|
||||||
{{ props.mvp }}
|
|
||||||
</td>
|
|
||||||
<td class="player__score">
|
|
||||||
{{ props.player_score }}
|
|
||||||
</td>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {constructAvatarUrl, DisplayRank, FormatVacDate, GetHLTV_1, GoToPlayer} from "@/utils";
|
|
||||||
import {useStore} from "vuex";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ScoreTeamPlayer',
|
|
||||||
props: {
|
|
||||||
steamid64: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
avatar: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
default: 'Avatar'
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
default: 'Name'
|
|
||||||
},
|
|
||||||
rank_old: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
rank_new: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
kills: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
assists: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
deaths: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
kdiff: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
hs: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
rounds_played: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
mk_duo: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
mk_triple: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
mk_quad: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
mk_pent: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
dmg: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
mvp: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
player_score: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
tracked: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
parsed: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
vac: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
vac_date: {
|
|
||||||
type: Number,
|
|
||||||
required: false,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
game_ban: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
game_ban_date: {
|
|
||||||
type: Number,
|
|
||||||
required: false,
|
|
||||||
default: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const store = useStore()
|
|
||||||
return {props, GetHLTV_1, GoToPlayer, DisplayRank, constructAvatarUrl, FormatVacDate, store}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.player__vac,
|
|
||||||
.vac-placeholder {
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player__vac {
|
|
||||||
img {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.player__avatar {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player__name {
|
|
||||||
text-align: left;
|
|
||||||
width: 150px;
|
|
||||||
max-width: 150px;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.tracked {
|
|
||||||
font-size: .8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa-external-link {
|
|
||||||
font-size: .8rem;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.player__rank,
|
|
||||||
.rank-placeholder {
|
|
||||||
width: 100px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 60px;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.player__kills, .player__assist, .player__deaths, .player__kdiff, .player__mvp {
|
|
||||||
width: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player__kd, .player__hs, .player__rating, .player__score {
|
|
||||||
width: 75px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player__adr {
|
|
||||||
width: 85px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player__rating {
|
|
||||||
border-radius: 25% 25%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<template>
|
|
||||||
<h3>This Graph will be available soon</h3>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {watch} from "vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "SprayGraph",
|
|
||||||
props: {
|
|
||||||
spray: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
watch(() => props.spray, () => {
|
|
||||||
// console.log(props.spray)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="player-dmg">
|
|
||||||
<div id="dmg-chart-1"></div>
|
|
||||||
<div id="dmg-chart-2"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import * as echarts from 'echarts/core';
|
|
||||||
import {GridComponent, LegendComponent, TooltipComponent} from 'echarts/components';
|
|
||||||
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 {
|
|
||||||
name: "FlashChart",
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
let myChart1, myChart2
|
|
||||||
|
|
||||||
const getWindowWidth = () => {
|
|
||||||
const windowWidth = window.innerWidth
|
|
||||||
if (windowWidth <= 750)
|
|
||||||
return windowWidth
|
|
||||||
else
|
|
||||||
return 650
|
|
||||||
}
|
|
||||||
|
|
||||||
const setHeight = () => {
|
|
||||||
const windowWidth = getWindowWidth()
|
|
||||||
if (windowWidth >= 751)
|
|
||||||
return windowWidth * 3 / 7.5
|
|
||||||
else if (windowWidth >= 501 && windowWidth <= 750)
|
|
||||||
return windowWidth * 3 / 6.5
|
|
||||||
else
|
|
||||||
return windowWidth * 3 / 5.5
|
|
||||||
}
|
|
||||||
|
|
||||||
const width = ref(getWindowWidth())
|
|
||||||
const height = ref(setHeight())
|
|
||||||
|
|
||||||
const dataArr = (stats, team, prop) => {
|
|
||||||
if (['team', 'enemy', 'self'].indexOf(prop) > -1) {
|
|
||||||
let arr = []
|
|
||||||
for (let i = (team - 1) * 5; i < team * 5; i++) {
|
|
||||||
arr.push({
|
|
||||||
value: checkStatEmpty(Function('return(function(stats, i){ return stats[i].dmg.' + prop + '})')()(stats, i)) * (prop === 'enemy' ? 1 : -1),
|
|
||||||
itemStyle: {
|
|
||||||
color: prop === 'enemy' ? getComputedStyle(document.documentElement).getPropertyValue(`--csgo-${stats[i].color}`) : 'firebrick'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
arr.reverse()
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const optionGen = (team) => {
|
|
||||||
return {
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
axisPointer: {
|
|
||||||
type: 'shadow'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
left: '3%',
|
|
||||||
right: '4%',
|
|
||||||
bottom: '3%',
|
|
||||||
containLabel: true
|
|
||||||
},
|
|
||||||
xAxis: [
|
|
||||||
{
|
|
||||||
type: 'value',
|
|
||||||
min: -300
|
|
||||||
}
|
|
||||||
],
|
|
||||||
yAxis: [
|
|
||||||
{
|
|
||||||
type: 'category',
|
|
||||||
axisTick: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
data: getPlayerArr(store.state.matchDetails.stats, team)
|
|
||||||
}
|
|
||||||
],
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'Team',
|
|
||||||
type: 'bar',
|
|
||||||
stack: 'Total',
|
|
||||||
label: {
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
emphasis: {
|
|
||||||
focus: 'series'
|
|
||||||
},
|
|
||||||
data: dataArr(store.state.matchDetails.stats, team, 'team')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Enemy',
|
|
||||||
type: 'bar',
|
|
||||||
stack: 'Total',
|
|
||||||
label: {
|
|
||||||
show: true,
|
|
||||||
position: 'inside'
|
|
||||||
},
|
|
||||||
emphasis: {
|
|
||||||
focus: 'series'
|
|
||||||
},
|
|
||||||
data: dataArr(store.state.matchDetails.stats, team, 'enemy')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const disposeCharts = () => {
|
|
||||||
if (myChart1 != null && myChart1 !== '' && myChart1 !== undefined) {
|
|
||||||
myChart1.dispose()
|
|
||||||
}
|
|
||||||
if (myChart2 != null && myChart2 !== '' && myChart2 !== undefined) {
|
|
||||||
myChart2.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildCharts = () => {
|
|
||||||
disposeCharts()
|
|
||||||
|
|
||||||
myChart1 = echarts.init(document.getElementById('dmg-chart-1'), {}, {width: width.value, height: height.value});
|
|
||||||
myChart1.setOption(optionGen(1));
|
|
||||||
|
|
||||||
myChart2 = echarts.init(document.getElementById('dmg-chart-2'), {}, {width: width.value, height: height.value});
|
|
||||||
myChart2.setOption(optionGen(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (store.state.matchDetails.stats) {
|
|
||||||
echarts.use([
|
|
||||||
TooltipComponent,
|
|
||||||
GridComponent,
|
|
||||||
LegendComponent,
|
|
||||||
BarChart,
|
|
||||||
CanvasRenderer
|
|
||||||
]);
|
|
||||||
|
|
||||||
buildCharts()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
disposeCharts()
|
|
||||||
})
|
|
||||||
|
|
||||||
window.onresize = () => {
|
|
||||||
if (window.innerWidth <= 750) {
|
|
||||||
width.value = getWindowWidth() - 20
|
|
||||||
height.value = setHeight()
|
|
||||||
}
|
|
||||||
|
|
||||||
buildCharts()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.player-dmg {
|
|
||||||
display: flex;
|
|
||||||
margin-bottom: 4rem;
|
|
||||||
|
|
||||||
#dmg-chart-1,
|
|
||||||
#dmg-chart-2 {
|
|
||||||
flex-basis: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
.player-dmg {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="toggle-btn text-muted">
|
|
||||||
<div @click.prevent="$emit('translated', handleBtnClick())"
|
|
||||||
class="d-flex">
|
|
||||||
<span class="text-center mx-2">
|
|
||||||
<i id="toggle-off" class="fa fa-toggle-off show"/>
|
|
||||||
<i id="toggle-on" class="fa fa-toggle-on"/>
|
|
||||||
</span>
|
|
||||||
<div>
|
|
||||||
<span :class="toggle === 'translated' ? 'text-warning' : ''"
|
|
||||||
class="float-start">
|
|
||||||
<span class="text-uppercase">Translate to {{data.browserLang}}</span>
|
|
||||||
<span class="loading-icon ms-2" title="Translating..">
|
|
||||||
<i class="fa fa-spinner fa-pulse fa-fw"/>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {onMounted, reactive, ref} from "vue";
|
|
||||||
import ISO6391 from 'iso-639-1'
|
|
||||||
import {GetChatHistoryTranslated} from "@/utils";
|
|
||||||
import {useStore} from "vuex";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'TranslateChatButton',
|
|
||||||
props: {
|
|
||||||
translated: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const data = reactive({
|
|
||||||
browserIsoCode: '',
|
|
||||||
browserLangCode: '',
|
|
||||||
browserLang: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
const toggle = ref('original')
|
|
||||||
|
|
||||||
const setLanguageVariables = () => {
|
|
||||||
const navLangs = navigator.languages
|
|
||||||
|
|
||||||
data.browserIsoCode = navLangs.find((l) => l.length === 5)
|
|
||||||
data.browserLangCode = navLangs[0]
|
|
||||||
|
|
||||||
if (ISO6391.validate(data.browserLangCode)) {
|
|
||||||
data.browserLang = ISO6391.getNativeName(data.browserLangCode)
|
|
||||||
} else {
|
|
||||||
data.browserIsoCode = 'en-US'
|
|
||||||
data.browserLangCode = 'en'
|
|
||||||
data.browserLang = 'English'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBtnClick = async () => {
|
|
||||||
let response
|
|
||||||
|
|
||||||
const refreshButton = document.querySelector('.loading-icon .fa-spinner')
|
|
||||||
refreshButton.classList.add('show')
|
|
||||||
|
|
||||||
toggleShow()
|
|
||||||
|
|
||||||
response = await GetChatHistoryTranslated(store, store.state.matchDetails.match_id)
|
|
||||||
|
|
||||||
if (refreshButton.classList.contains('show'))
|
|
||||||
refreshButton.classList.remove('show')
|
|
||||||
|
|
||||||
return [response, toggle.value]
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleShow = () => {
|
|
||||||
const offBtn = document.getElementById('toggle-off')
|
|
||||||
const onBtn = document.getElementById('toggle-on')
|
|
||||||
|
|
||||||
if (offBtn.classList.contains('show')) {
|
|
||||||
offBtn.classList.remove('show')
|
|
||||||
onBtn.classList.add('show')
|
|
||||||
toggle.value = 'translated'
|
|
||||||
} else if (onBtn.classList.contains('show')) {
|
|
||||||
onBtn.classList.remove('show')
|
|
||||||
offBtn.classList.add('show')
|
|
||||||
toggle.value = 'original'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
setLanguageVariables()
|
|
||||||
})
|
|
||||||
return {data, toggle, handleBtnClick}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.toggle-btn {
|
|
||||||
margin: 0 auto;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.fa {
|
|
||||||
display: none;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
vertical-align: middle;
|
|
||||||
|
|
||||||
&.show {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div :style="props.ud.flames || props.ud.flash || props.ud.he ? 'display: flex' : 'display: none'"
|
|
||||||
class="player-utility">
|
|
||||||
<div class="heading">
|
|
||||||
<img :src="props.avatar" alt="Player avatar" class="avatar">
|
|
||||||
<h4>{{ props.name }}</h4>
|
|
||||||
</div>
|
|
||||||
<div :id="'utility-chart-' + props.id"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import * as echarts from 'echarts/core';
|
|
||||||
import {LegendComponent, TooltipComponent} from 'echarts/components';
|
|
||||||
import {PieChart} from 'echarts/charts';
|
|
||||||
import {LabelLayout} from 'echarts/features';
|
|
||||||
import {CanvasRenderer} from 'echarts/renderers';
|
|
||||||
import { TitleComponent } from 'echarts/components';
|
|
||||||
import {onMounted} from "vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "FlashChart",
|
|
||||||
props: {
|
|
||||||
id: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
avatar: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
ud: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
onMounted(() => {
|
|
||||||
echarts.use([
|
|
||||||
TooltipComponent,
|
|
||||||
LegendComponent,
|
|
||||||
PieChart,
|
|
||||||
CanvasRenderer,
|
|
||||||
TitleComponent,
|
|
||||||
LabelLayout
|
|
||||||
]);
|
|
||||||
|
|
||||||
let myChart = echarts.init(document.getElementById(`utility-chart-${props.id}`), {}, {width: 500, height: 300});
|
|
||||||
let option
|
|
||||||
|
|
||||||
option = {
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'item',
|
|
||||||
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'Utility Damage',
|
|
||||||
type: 'pie',
|
|
||||||
radius: [0, '65%'],
|
|
||||||
avoidLabelOverlap: true,
|
|
||||||
itemStyle: {
|
|
||||||
borderRadius: 10,
|
|
||||||
borderColor: '#000',
|
|
||||||
borderWidth: 3
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
position: 'inside',
|
|
||||||
fontsize: 36,
|
|
||||||
fontWeight: 'bold'
|
|
||||||
},
|
|
||||||
labelLine: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
data: [
|
|
||||||
(props.ud.flames ? {
|
|
||||||
value: props.ud.flames ? props.ud.flames : null,
|
|
||||||
name: 'Flames',
|
|
||||||
itemStyle: {
|
|
||||||
color: '#FF4343FF'
|
|
||||||
}
|
|
||||||
} : {}),
|
|
||||||
(props.ud.he ? {
|
|
||||||
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: {
|
|
||||||
color: '#18cff3'
|
|
||||||
}
|
|
||||||
} : {}),
|
|
||||||
(props.ud.smoke ? {
|
|
||||||
value: props.ud.smoke ? props.ud.smoke : null,
|
|
||||||
name: 'Smoke',
|
|
||||||
itemStyle: {
|
|
||||||
color: '#6e6b78'
|
|
||||||
}
|
|
||||||
} : {}),
|
|
||||||
(props.ud.decoy ? {
|
|
||||||
value: props.ud.decoy ? props.ud.decoy : null,
|
|
||||||
name: 'Decoy',
|
|
||||||
itemStyle: {
|
|
||||||
color: '#e28428'
|
|
||||||
}
|
|
||||||
} : {})
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
myChart.setOption(option);
|
|
||||||
})
|
|
||||||
|
|
||||||
return {props}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.player-utility {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.heading {
|
|
||||||
display: flex;
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: -30px;
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-right: 20px;
|
|
||||||
color: #ff4343;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin-top: 7px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
padding-top: 40px;
|
|
||||||
margin-bottom: -20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@for $i from 0 through 9 {
|
|
||||||
#utility-chart-#{$i} {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="utility-chart-total" v-if="props.stats">
|
|
||||||
<div class="heading">
|
|
||||||
<h4>Total Utility Damage</h4>
|
|
||||||
</div>
|
|
||||||
<div id="utility-chart-total"></div>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import * as echarts from 'echarts/core';
|
|
||||||
import {GridComponent, LegendComponent, TooltipComponent} from 'echarts/components';
|
|
||||||
import {BarChart} from 'echarts/charts';
|
|
||||||
import {CanvasRenderer} from 'echarts/renderers';
|
|
||||||
import {onMounted} from "vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "FlashChart",
|
|
||||||
props: {
|
|
||||||
stats: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const checkStatEmpty = (stat) => {
|
|
||||||
if (stat)
|
|
||||||
return stat
|
|
||||||
else
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const seriesArr = (stats) => {
|
|
||||||
let arr = []
|
|
||||||
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)
|
|
||||||
|
|
||||||
if (sum !== 0) {
|
|
||||||
arr.push({
|
|
||||||
name: stats[i].player.name,
|
|
||||||
type: 'bar',
|
|
||||||
stack: 'total',
|
|
||||||
label: {
|
|
||||||
show: true
|
|
||||||
},
|
|
||||||
emphasis: {
|
|
||||||
focus: 'series'
|
|
||||||
},
|
|
||||||
data: [sum]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
arr.sort((a, b) => parseFloat(b.data[0]) - parseFloat(a.data[0]))
|
|
||||||
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
echarts.use([
|
|
||||||
TooltipComponent,
|
|
||||||
GridComponent,
|
|
||||||
LegendComponent,
|
|
||||||
BarChart,
|
|
||||||
CanvasRenderer
|
|
||||||
]);
|
|
||||||
|
|
||||||
let myChart = echarts.init(document.getElementById('utility-chart-total'), {}, {width: 800, height: 200});
|
|
||||||
let option
|
|
||||||
|
|
||||||
option = {
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
axisPointer: {
|
|
||||||
// Use axis to trigger tooltip
|
|
||||||
type: 'shadow' // 'shadow' as default; can also be 'line'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// color: ['#143147', '#39546c', '#617a94', '#89a2bd', '#b3cce8', '#eac65c', '#bd9d2c', '#917501', '#685000', '#412c00'],
|
|
||||||
// color: ['#003470', '#005a9b', '#0982c7', '#4bace5', '#90d3fe', '#febf4a', '#d7931c', '#ac6a01', '#804400', '#572000'],
|
|
||||||
// color: ['#888F98', '#10121A', '#1B2732', '#5F7892', '#C3A235'],
|
|
||||||
legend: {
|
|
||||||
textStyle: {
|
|
||||||
color: 'white'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
left: '3%',
|
|
||||||
right: '4%',
|
|
||||||
bottom: '3%',
|
|
||||||
containLabel: true
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'value'
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'category',
|
|
||||||
data: ['Total']
|
|
||||||
},
|
|
||||||
aria: {
|
|
||||||
enabled: true,
|
|
||||||
show: true,
|
|
||||||
decal: {
|
|
||||||
show: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
series: seriesArr(props.stats)
|
|
||||||
};
|
|
||||||
|
|
||||||
myChart.setOption(option);
|
|
||||||
})
|
|
||||||
|
|
||||||
return {props}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.utility-chart-total {
|
|
||||||
.heading {
|
|
||||||
display: flex;
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: -30px;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin: 7px auto 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
padding-top: 40px;
|
|
||||||
margin-bottom: -20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#utility-chart-total {
|
|
||||||
margin: 40px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -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
|
|
||||||
25
src/main.js
25
src/main.js
@@ -1,25 +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,
|
|
||||||
disableCookies: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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('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('CompDetails')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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('Error404')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/500',
|
|
||||||
name: '500',
|
|
||||||
components: {
|
|
||||||
main: lazyLoadErrorPages('Error500')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/502',
|
|
||||||
name: '502',
|
|
||||||
components: {
|
|
||||||
main: lazyLoadErrorPages('Error502')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
.helptext {
|
|
||||||
cursor: help;
|
|
||||||
text-decoration: underline dotted grey;
|
|
||||||
}
|
|
||||||
.helpicon {
|
|
||||||
cursor: help;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uprank {
|
|
||||||
box-shadow: 0 0 20px greenyellow;
|
|
||||||
border: 1px solid greenyellow;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.downrank {
|
|
||||||
box-shadow: 0 0 20px #ff2f2f;
|
|
||||||
border: 1px solid #ff2f2f;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-wave-alt {
|
|
||||||
mask-image: linear-gradient(130deg, black 55%, rgba(0, 0, 0, (1 - 0.2)) 75%, black 95%);
|
|
||||||
mask-size: 200% 100%;
|
|
||||||
animation: placeholder-wave-alt 2.5s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.team-color-blue,
|
|
||||||
.team-color-orange,
|
|
||||||
.team-color-green,
|
|
||||||
.team-color-purple,
|
|
||||||
.team-color-yellow,
|
|
||||||
.team-color-grey {
|
|
||||||
outline: 3px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.team-color-grey {
|
|
||||||
outline-color: var(--csgo-grey);
|
|
||||||
}
|
|
||||||
|
|
||||||
.team-color-orange {
|
|
||||||
outline-color: var(--csgo-orange);
|
|
||||||
}
|
|
||||||
|
|
||||||
.team-color-blue {
|
|
||||||
outline-color: var(--csgo-blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
.team-color-yellow {
|
|
||||||
outline-color: var(--csgo-yellow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.team-color-purple {
|
|
||||||
outline-color: var(--csgo-purple);
|
|
||||||
}
|
|
||||||
|
|
||||||
.team-color-green {
|
|
||||||
outline-color: var(--csgo-green);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes placeholder-wave-alt {
|
|
||||||
100% {
|
|
||||||
mask-position: -200% 0%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// Custom.scss
|
|
||||||
|
|
||||||
@import "~@fontsource/orbitron/index.css";
|
|
||||||
@import "~@fontsource/open-sans/index.css";
|
|
||||||
@import "~@fontsource/open-sans/variable-full.css";
|
|
||||||
@import "~fork-awesome/css/fork-awesome.css";
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "CSRegular";
|
|
||||||
src: local('CSRegular'),
|
|
||||||
url("../../public/fonts/cs_regular.woff2") format("woff2"),
|
|
||||||
url("../../public/fonts/cs_regular.woff2") format("truetype");
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default variable overrides
|
|
||||||
$font-family-base: 'Open SansVariable';
|
|
||||||
$body-color: white;
|
|
||||||
|
|
||||||
$primary: #888f98;
|
|
||||||
$secondary: #10121a;
|
|
||||||
$body-bg: #1b2732;
|
|
||||||
$blue: #5f7892;
|
|
||||||
$warning: #c3a235;
|
|
||||||
$info: $blue;
|
|
||||||
$success: #609926;
|
|
||||||
|
|
||||||
// Custom classes
|
|
||||||
.mt-n5 {
|
|
||||||
margin-top: -3rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-n4 {
|
|
||||||
margin-top: -2rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bootstrap
|
|
||||||
@import "~bootstrap/scss/bootstrap";
|
|
||||||
|
|
||||||
:root {
|
|
||||||
// CSGO COLORS
|
|
||||||
--csgo-orange: #FE9A28;
|
|
||||||
--csgo-blue: #5BA7FE;
|
|
||||||
--csgo-yellow: #F7F52F;
|
|
||||||
--csgo-purple: #A01BEF;
|
|
||||||
--csgo-green: #04B462;
|
|
||||||
--csgo-grey: #5a5a5a;
|
|
||||||
}
|
|
||||||
|
|
||||||
@import "classes";
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import { createStore } from 'vuex'
|
|
||||||
|
|
||||||
export default createStore({
|
|
||||||
state: {
|
|
||||||
id64: '',
|
|
||||||
vanityUrl: '',
|
|
||||||
matchDetails: {},
|
|
||||||
playerDetails: {},
|
|
||||||
playersArr: [],
|
|
||||||
scroll_state: 0,
|
|
||||||
info: []
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
changeId64(state, payload) {
|
|
||||||
state.id64 = payload.id
|
|
||||||
},
|
|
||||||
changeVanityUrl(state, payload) {
|
|
||||||
state.vanityUrl = payload.id
|
|
||||||
},
|
|
||||||
changeMatchDetails(state, payload) {
|
|
||||||
state.matchDetails = payload.data
|
|
||||||
},
|
|
||||||
changePlayerDetails(state, payload) {
|
|
||||||
state.playerDetails = payload.data
|
|
||||||
},
|
|
||||||
changePlayersArr(state, payload) {
|
|
||||||
state.playersArr = payload.data
|
|
||||||
},
|
|
||||||
changeScrollState(state, payload) {
|
|
||||||
state.scroll_state = payload
|
|
||||||
},
|
|
||||||
changeInfoState(state, payload) {
|
|
||||||
state.info.push(payload.data)
|
|
||||||
},
|
|
||||||
resetId64(state) {
|
|
||||||
state.id64 = ''
|
|
||||||
},
|
|
||||||
resetVanityUrl(state) {
|
|
||||||
state.vanityUrl = ''
|
|
||||||
},
|
|
||||||
resetMatchDetails(state) {
|
|
||||||
state.matchDetails = {}
|
|
||||||
},
|
|
||||||
resetPlayerDetails(state) {
|
|
||||||
state.playerDetails = {}
|
|
||||||
},
|
|
||||||
resetPlayersArr(state) {
|
|
||||||
state.playersArr = []
|
|
||||||
},
|
|
||||||
resetScrollState(state) {
|
|
||||||
state.scroll_state = 0
|
|
||||||
},
|
|
||||||
resetInfoState(state) {
|
|
||||||
state.info = []
|
|
||||||
},
|
|
||||||
removeInfoState(state, id) {
|
|
||||||
state.info.splice(id, 1)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
},
|
|
||||||
modules: {
|
|
||||||
},
|
|
||||||
getters: {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import {DateTime, Duration} from "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;
|
|
||||||
}
|
|
||||||
@@ -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}.png`
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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: 'ExploreView',
|
|
||||||
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>
|
|
||||||
@@ -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: 'HomeView',
|
|
||||||
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("../../public/images/map_screenshots/default.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
.head {
|
|
||||||
// display webp if possible
|
|
||||||
background-image: url("../../public/images/map_screenshots/default.png");
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-up {
|
|
||||||
font-family: "Open Sans", 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>
|
|
||||||
@@ -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";
|
|
||||||
import {FOOTER_HEIGHT, NAV_HEIGHT} from "@/constants";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'MatchView',
|
|
||||||
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>
|
|
||||||
@@ -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: 'PlayerView',
|
|
||||||
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>
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="container mt-4">
|
|
||||||
<h4>Privacy Policy</h4>
|
|
||||||
|
|
||||||
<p>Data protection is of a particularly high priority for us. The use of CSGOWTF is possible without any indication of personal data; however, if a data subject wants to use special services via our website, processing of personal data could become necessary. If the processing of personal data is necessary and there is no statutory basis for such processing, we generally obtain consent from the data subject.</p>
|
|
||||||
|
|
||||||
<p>The processing of personal data, such as the name, address, e-mail address, or telephone number of a data subject shall always be in line with the General Data Protection Regulation (GDPR), and in accordance with the country-specific data protection regulations applicable. By means of this data protection declaration, we would like to inform the general public of the nature, scope, and purpose of the personal data we collect, use and process. Furthermore, data subjects are informed, by means of this data protection declaration, of the rights to which they are entitled.</p>
|
|
||||||
|
|
||||||
<p>As the controller, we have implemented numerous technical and organizational measures to ensure the most complete protection of personal data processed through this website. However, Internet-based data transmissions may in principle have security gaps, so absolute protection may not be guaranteed.</p>
|
|
||||||
|
|
||||||
<h4>1. Definitions</h4>
|
|
||||||
<p>This data protection declaration is based on the terms used by the European legislator for the adoption of the General Data Protection Regulation (GDPR). Our data protection declaration should be legible and understandable for the general public, as well as our users. To ensure this, we would like to first explain the terminology used.</p>
|
|
||||||
|
|
||||||
<p>In this data protection declaration, we use, inter alia, the following terms:</p>
|
|
||||||
|
|
||||||
<ul style="list-style: none">
|
|
||||||
<li><h4>a) Personal data</h4>
|
|
||||||
<p>Personal data means any information relating to an identified or identifiable natural person (“data subject”). An identifiable natural person is one who can be identified, directly or indirectly, in particular by reference to an identifier such as a name, an identification number, location data, an online identifier or to one or more factors specific to the physical, physiological, genetic, mental, economic, cultural or social identity of that natural person.</p>
|
|
||||||
</li>
|
|
||||||
<li><h4>b) Data subject</h4>
|
|
||||||
<p>Data subject is any identified or identifiable natural person, whose personal data is processed by the controller responsible for the processing.</p>
|
|
||||||
</li>
|
|
||||||
<li><h4>c) Processing</h4>
|
|
||||||
<p>Processing is any operation or set of operations which is performed on personal data or on sets of personal data, whether or not by automated means, such as collection, recording, organisation, structuring, storage, adaptation or alteration, retrieval, consultation, use, disclosure by transmission, dissemination or otherwise making available, alignment or combination, restriction, erasure or destruction. </p>
|
|
||||||
</li>
|
|
||||||
<li><h4>d) Restriction of processing</h4>
|
|
||||||
<p>Restriction of processing is the marking of stored personal data with the aim of limiting their processing in the future. </p>
|
|
||||||
</li>
|
|
||||||
<li><h4>e) Profiling</h4>
|
|
||||||
<p>Profiling means any form of automated processing of personal data consisting of the use of personal data to evaluate certain personal aspects relating to a natural person, in particular to analyse or predict aspects concerning that natural person's performance at work, economic situation, health, personal preferences, interests, reliability, behaviour, location or movements. </p>
|
|
||||||
</li>
|
|
||||||
<li><h4>f) Pseudonymisation</h4>
|
|
||||||
<p>Pseudonymisation is the processing of personal data in such a manner that the personal data can no longer be attributed to a specific data subject without the use of additional information, provided that such additional information is kept separately and is subject to technical and organisational measures to ensure that the personal data are not attributed to an identified or identifiable natural person. </p>
|
|
||||||
</li>
|
|
||||||
<li><h4>g) Controller or controller responsible for the processing</h4>
|
|
||||||
<p>Controller or controller responsible for the processing is the natural or legal person, public authority, agency or other body which, alone or jointly with others, determines the purposes and means of the processing of personal data; where the purposes and means of such processing are determined by Union or Member State law, the controller or the specific criteria for its nomination may be provided for by Union or Member State law. </p>
|
|
||||||
</li>
|
|
||||||
<li><h4>h) Processor</h4>
|
|
||||||
<p>Processor is a natural or legal person, public authority, agency or other body which processes personal data on behalf of the controller. </p>
|
|
||||||
</li>
|
|
||||||
<li><h4>i) Recipient</h4>
|
|
||||||
<p>Recipient is a natural or legal person, public authority, agency or another body, to which the personal data are disclosed, whether a third party or not. However, public authorities which may receive personal data in the framework of a particular inquiry in accordance with Union or Member State law shall not be regarded as recipients; the processing of those data by those public authorities shall be in compliance with the applicable data protection rules according to the purposes of the processing. </p>
|
|
||||||
</li>
|
|
||||||
<li><h4>j) Third party</h4>
|
|
||||||
<p>Third party is a natural or legal person, public authority, agency or body other than the data subject, controller, processor and persons who, under the direct authority of the controller or processor, are authorised to process personal data.</p>
|
|
||||||
</li>
|
|
||||||
<li><h4>k) Consent</h4>
|
|
||||||
<p>Consent of the data subject is any freely given, specific, informed and unambiguous indication of the data subject's wishes by which he or she, by a statement or by a clear affirmative action, signifies agreement to the processing of personal data relating to him or her. </p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>2. Name and Address of the controller</h4>
|
|
||||||
<p>Controller for the purposes of the General Data Protection Regulation (GDPR), other data protection laws applicable in Member states of the European Union and other provisions related to data protection is:
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>CSGOWTF Team</p>
|
|
||||||
<p>Email: privacy@csgow.tf</p>
|
|
||||||
<p>Website: csgow.tf</p>
|
|
||||||
|
|
||||||
<h4>3. Cookies</h4>
|
|
||||||
<p>We use cookies. Cookies are text files that are stored in a computer system via an Internet browser.</p>
|
|
||||||
|
|
||||||
<p>Many Internet sites and servers use cookies. Many cookies contain a so-called cookie ID. A cookie ID is a unique identifier of the cookie. It consists of a character string through which Internet pages and servers can be assigned to the specific Internet browser in which the cookie was stored. This allows visited Internet sites and servers to differentiate the individual browser of the subject from other Internet browsers that contain other cookies. A specific Internet browser can be recognized and identified using the unique cookie ID.</p>
|
|
||||||
|
|
||||||
<p>Through the use of cookies, we can provide users of this website with more user-friendly services that would not be possible without the cookie setting.</p>
|
|
||||||
|
|
||||||
<p>By means of a cookie, the information and offers on our website can be optimized with the user in mind. Cookies allow us, as previously mentioned, to recognize our website users. The purpose of this recognition is to make it easier for users to utilize our website. The website user that uses cookies, e.g. does not have to enter access data each time the website is accessed, because this is taken over by the website, and the cookie is thus stored on the user's computer system. Another example is the cookie of a shopping cart in an online shop. The online store remembers the articles that a customer has placed in the virtual shopping cart via a cookie.</p>
|
|
||||||
|
|
||||||
<p>The data subject may, at any time, prevent the setting of cookies through our website by means of a corresponding setting of the Internet browser used, and may thus permanently deny the setting of cookies. Furthermore, already set cookies may be deleted at any time via an Internet browser or other software programs. This is possible in all popular Internet browsers. If the data subject deactivates the setting of cookies in the Internet browser used, not all functions of our website may be entirely usable.</p>
|
|
||||||
|
|
||||||
<h4>4. Collection of general data and information</h4>
|
|
||||||
<p>We collect a series of general data and information when a data subject or automated system calls up the website. This general data and information are stored in the server log files. Collected may be (1) the browser types and versions used, (2) the operating system used by the accessing system, (3) the website from which an accessing system reaches our website (so-called referrers), (4) the sub-websites, (5) the date and time of access to the Internet site, (6) an Internet protocol address (IP address), (7) the Internet service provider of the accessing system, and (8) any other similar data and information that may be used in the event of attacks on our information technology systems.</p>
|
|
||||||
|
|
||||||
<p>When using these general data and information, we do not draw any conclusions about the data subject. Rather, this information is needed to (1) deliver the content of our website correctly, (2) optimize the content of our website as well as its advertisement, (3) ensure the long-term viability of our information technology systems and website technology, and (4) provide law enforcement authorities with the information necessary for criminal prosecution in case of a cyber-attack. Therefore, we analyze anonymously collected data and information statistically, with the aim of increasing the data protection and data security of our site, and to ensure an optimal level of protection for the personal data we process. The anonymous data of the server log files are stored separately from all personal data provided by a data subject.</p>
|
|
||||||
|
|
||||||
<h4>5. Registration on our website</h4>
|
|
||||||
<p>The data subject has the possibility to register on the website of the controller with the indication of personal data. Which personal data are transmitted to the controller is determined by the respective input mask used for the registration. The personal data entered by the data subject are collected and stored exclusively for internal use by the controller, and for his own purposes. The controller may request transfer to one or more processors (e.g. a parcel service) that also uses personal data for an internal purpose which is attributable to the controller.</p>
|
|
||||||
|
|
||||||
<p>By registering on the website of the controller, the IP address—assigned by the Internet service provider (ISP) and used by the data subject—date, and time of the registration are also stored. The storage of this data takes place against the background that this is the only way to prevent the misuse of our services, and, if necessary, to make it possible to investigate committed offenses. Insofar, the storage of this data is necessary to secure the controller. This data is not passed on to third parties unless there is a statutory obligation to pass on the data, or if the transfer serves the aim of criminal prosecution.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>The registration of the data subject, with the voluntary indication of personal data, is intended to enable the controller to offer the data subject contents or services that may only be offered to registered users due to the nature of the matter in question. Registered persons are free to change the personal data specified during the registration at any time, or to have them completely deleted from the data stock of the controller.</p>
|
|
||||||
|
|
||||||
<p>The data controller shall, at any time, provide information upon request to each data subject as to what personal data are stored about the data subject. In addition, the data controller shall correct or erase personal data at the request or indication of the data subject, insofar as there are no statutory storage obligations. The entirety of the controller’s employees are available to the data subject in this respect as contact persons.</p>
|
|
||||||
|
|
||||||
<h4>6. Routine erasure and blocking of personal data</h4>
|
|
||||||
<p>The data controller shall process and store the personal data of the data subject only for the period necessary to achieve the purpose of storage, or as far as this is granted by the European legislator or other legislators in laws or regulations to which the controller is subject to.</p>
|
|
||||||
|
|
||||||
<p>If the storage purpose is not applicable, or if a storage period prescribed by the European legislator or another competent legislator expires, the personal data are routinely blocked or erased in accordance with legal requirements.</p>
|
|
||||||
|
|
||||||
<h4>7. Rights of the data subject</h4>
|
|
||||||
<ul style="list-style: none;">
|
|
||||||
<li><h4>a) Right of confirmation</h4>
|
|
||||||
<p>Each data subject shall have the right granted by the European legislator to obtain from the controller the confirmation as to whether or not personal data concerning him or her are being processed. If a data subject wishes to avail himself of this right of confirmation, he or she may, at any time, contact any employee of the controller.</p>
|
|
||||||
</li>
|
|
||||||
<li><h4>b) Right of access</h4>
|
|
||||||
<p>Each data subject shall have the right granted by the European legislator to obtain from the controller free information about his or her personal data stored at any time and a copy of this information. Furthermore, the European directives and regulations grant the data subject access to the following information:</p>
|
|
||||||
|
|
||||||
<ul style="list-style: none;">
|
|
||||||
<li>the purposes of the processing;</li>
|
|
||||||
<li>the categories of personal data concerned;</li>
|
|
||||||
<li>the recipients or categories of recipients to whom the personal data have been or will be disclosed, in particular recipients in third countries or international organisations;</li>
|
|
||||||
<li>where possible, the envisaged period for which the personal data will be stored, or, if not possible, the criteria used to determine that period;</li>
|
|
||||||
<li>the existence of the right to request from the controller rectification or erasure of personal data, or restriction of processing of personal data concerning the data subject, or to object to such processing;</li>
|
|
||||||
<li>the existence of the right to lodge a complaint with a supervisory authority;</li>
|
|
||||||
<li>where the personal data are not collected from the data subject, any available information as to their source;</li>
|
|
||||||
<li>the existence of automated decision-making, including profiling, referred to in Article 22(1) and (4) of the GDPR and, at least in those cases, meaningful information about the logic involved, as well as the significance and envisaged consequences of such processing for the data subject.</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
<p>Furthermore, the data subject shall have a right to obtain information as to whether personal data are transferred to a third country or to an international organisation. Where this is the case, the data subject shall have the right to be informed of the appropriate safeguards relating to the transfer.</p>
|
|
||||||
|
|
||||||
<p>If a data subject wishes to avail himself of this right of access, he or she may, at any time, contact any employee of the controller.</p>
|
|
||||||
</li>
|
|
||||||
<li><h4>c) Right to rectification </h4>
|
|
||||||
<p>Each data subject shall have the right granted by the European legislator to obtain from the controller without undue delay the rectification of inaccurate personal data concerning him or her. Taking into account the purposes of the processing, the data subject shall have the right to have incomplete personal data completed, including by means of providing a supplementary statement.</p>
|
|
||||||
|
|
||||||
<p>If a data subject wishes to exercise this right to rectification, he or she may, at any time, contact any employee of the controller.</p></li>
|
|
||||||
<li>
|
|
||||||
<h4>d) Right to erasure (Right to be forgotten) </h4>
|
|
||||||
<p>Each data subject shall have the right granted by the European legislator to obtain from the controller the erasure of personal data concerning him or her without undue delay, and the controller shall have the obligation to erase personal data without undue delay where one of the following grounds applies, as long as the processing is not necessary: </p>
|
|
||||||
|
|
||||||
<ul style="list-style: none;">
|
|
||||||
<li>The personal data are no longer necessary in relation to the purposes for which they were collected or otherwise processed.</li>
|
|
||||||
<li>The data subject withdraws consent to which the processing is based according to point (a) of Article 6(1) of the GDPR, or point (a) of Article 9(2) of the GDPR, and where there is no other legal ground for the processing.</li>
|
|
||||||
<li>The data subject objects to the processing pursuant to Article 21(1) of the GDPR and there are no overriding legitimate grounds for the processing, or the data subject objects to the processing pursuant to Article 21(2) of the GDPR. </li>
|
|
||||||
<li>The personal data have been unlawfully processed.</li>
|
|
||||||
<li>The personal data must be erased for compliance with a legal obligation in Union or Member State law to which the controller is subject.</li>
|
|
||||||
<li>The personal data have been collected in relation to the offer of information society services referred to in Article 8(1) of the GDPR.</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
<p>If one of the aforementioned reasons applies, and a data subject wishes to request the erasure of personal data stored by us, he or she may, at any time, contact us. We shall promptly ensure that the erasure request is complied with immediately.</p>
|
|
||||||
|
|
||||||
<p>Where the controller has made personal data public and is obliged pursuant to Article 17(1) to erase the personal data, the controller, taking account of available technology and the cost of implementation, shall take reasonable steps, including technical measures, to inform other controllers processing the personal data that the data subject has requested erasure by such controllers of any links to, or copy or replication of, those personal data, as far as processing is not required. We will arrange the necessary measures in individual cases.</p>
|
|
||||||
</li>
|
|
||||||
<li><h4>e) Right of restriction of processing</h4>
|
|
||||||
<p>Each data subject shall have the right granted by the European legislator to obtain from the controller restriction of processing where one of the following applies:</p>
|
|
||||||
|
|
||||||
<ul style="list-style: none;">
|
|
||||||
<li>The accuracy of the personal data is contested by the data subject, for a period enabling the controller to verify the accuracy of the personal data. </li>
|
|
||||||
<li>The processing is unlawful and the data subject opposes the erasure of the personal data and requests instead the restriction of their use instead.</li>
|
|
||||||
<li>The controller no longer needs the personal data for the purposes of the processing, but they are required by the data subject for the establishment, exercise or defence of legal claims.</li>
|
|
||||||
<li>The data subject has objected to processing pursuant to Article 21(1) of the GDPR pending the verification whether the legitimate grounds of the controller override those of the data subject.</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
<p>If one of the aforementioned conditions is met, and a data subject wishes to request the restriction of the processing of personal data stored by us, he or she may at any time contact us. We will arrange the restriction of the processing. </p>
|
|
||||||
</li>
|
|
||||||
<li><h4>f) Right to data portability</h4>
|
|
||||||
<p>Each data subject shall have the right granted by the European legislator, to receive the personal data concerning him or her, which was provided to a controller, in a structured, commonly used and machine-readable format. He or she shall have the right to transmit those data to another controller without hindrance from the controller to which the personal data have been provided, as long as the processing is based on consent pursuant to point (a) of Article 6(1) of the GDPR or point (a) of Article 9(2) of the GDPR, or on a contract pursuant to point (b) of Article 6(1) of the GDPR, and the processing is carried out by automated means, as long as the processing is not necessary for the performance of a task carried out in the public interest or in the exercise of official authority vested in the controller.</p>
|
|
||||||
|
|
||||||
<p>Furthermore, in exercising his or her right to data portability pursuant to Article 20(1) of the GDPR, the data subject shall have the right to have personal data transmitted directly from one controller to another, where technically feasible and when doing so does not adversely affect the rights and freedoms of others.</p>
|
|
||||||
|
|
||||||
<p>In order to assert the right to data portability, the data subject may at any time contact us.</p>
|
|
||||||
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h4>g) Right to object</h4>
|
|
||||||
<p>Each data subject shall have the right granted by the European legislator to object, on grounds relating to his or her particular situation, at any time, to processing of personal data concerning him or her, which is based on point (e) or (f) of Article 6(1) of the GDPR. This also applies to profiling based on these provisions.</p>
|
|
||||||
|
|
||||||
<p>We shall no longer process the personal data in the event of the objection, unless we can demonstrate compelling legitimate grounds for the processing which override the interests, rights and freedoms of the data subject, or for the establishment, exercise or defence of legal claims.</p>
|
|
||||||
|
|
||||||
<p>If we processes personal data for direct marketing purposes, the data subject shall have the right to object at any time to processing of personal data concerning him or her for such marketing. This applies to profiling to the extent that it is related to such direct marketing. If the data subject objects to us to the processing for direct marketing purposes, we will no longer process the personal data for these purposes.</p>
|
|
||||||
|
|
||||||
<p>In addition, the data subject has the right, on grounds relating to his or her particular situation, to object to processing of personal data concerning him or her by us for scientific or historical research purposes, or for statistical purposes pursuant to Article 89(1) of the GDPR, unless the processing is necessary for the performance of a task carried out for reasons of public interest.</p>
|
|
||||||
|
|
||||||
<p>In order to exercise the right to object, the data subject may contact us. In addition, the data subject is free in the context of the use of information society services, and notwithstanding Directive 2002/58/EC, to use his or her right to object by automated means using technical specifications.</p>
|
|
||||||
</li>
|
|
||||||
<li><h4>h) Automated individual decision-making, including profiling</h4>
|
|
||||||
<p>Each data subject shall have the right granted by the European legislator not to be subject to a decision based solely on automated processing, including profiling, which produces legal effects concerning him or her, or similarly significantly affects him or her, as long as the decision (1) is not is necessary for entering into, or the performance of, a contract between the data subject and a data controller, or (2) is not authorised by Union or Member State law to which the controller is subject and which also lays down suitable measures to safeguard the data subject's rights and freedoms and legitimate interests, or (3) is not based on the data subject's explicit consent.</p>
|
|
||||||
|
|
||||||
<p>If the decision (1) is necessary for entering into, or the performance of, a contract between the data subject and a data controller, or (2) it is based on the data subject's explicit consent, we shall implement suitable measures to safeguard the data subject's rights and freedoms and legitimate interests, at least the right to obtain human intervention on the part of the controller, to express his or her point of view and contest the decision.</p>
|
|
||||||
|
|
||||||
<p>If the data subject wishes to exercise the rights concerning automated individual decision-making, he or she may, at any time, contact us.</p>
|
|
||||||
|
|
||||||
</li>
|
|
||||||
<li><h4>i) Right to withdraw data protection consent </h4>
|
|
||||||
<p>Each data subject shall have the right granted by the European legislator to withdraw his or her consent to processing of his or her personal data at any time. </p>
|
|
||||||
|
|
||||||
<p>If the data subject wishes to exercise the right to withdraw the consent, he or she may, at any time, contact us.</p>
|
|
||||||
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<h4>8. Data protection provisions about the application and use of Matomo</h4>
|
|
||||||
<p>On this website, the controller has integrated the Matomo component. Matomo is an open-source software tool for web analysis. Web analysis is the collection, gathering and evaluation of data on the behavior of visitors from Internet sites. A web analysis tool collects, inter alia, data on the website from which a data subject came to a website (so-called referrer), which pages of the website were accessed or how often and for which period of time a sub-page was viewed. A web analysis is mainly used for the optimization of a website and the cost-benefit analysis of Internet advertising.</p>
|
|
||||||
|
|
||||||
<p>The software is operated on the server of the controller, the data protection-sensitive log files are stored exclusively on this server.</p>
|
|
||||||
|
|
||||||
<p>The purpose of the Matomo component is the analysis of the visitor flows on our website. The controller uses the obtained data and information, inter alia, to evaluate the use of this website in order to compile online reports, which show the activities on our Internet pages.</p>
|
|
||||||
|
|
||||||
<p>Matomo sets a cookie on the information technology system of the data subject. The definition of cookies is explained above. With the setting of the cookie, an analysis of the use of our website is enabled. With each call-up to one of the individual pages of this website, the Internet browser on the information technology system of the data subject is automatically through the Matomo component prompted to submit data for the purpose of online analysis to our server. During the course of this technical procedure, we obtain knowledge about personal information, such as the IP address of the data subject, which serves to understand the origin of visitors and clicks.</p>
|
|
||||||
|
|
||||||
<p>The cookie is used to store personal information, such as the access time, the location from which access was made, and the frequency of visits to our website. With each visit of our Internet pages, these personal data, including the IP address of the Internet access used by the data subject, are transferred to our server. These personal data will be stored by us. We do not forward this personal data to third parties.</p>
|
|
||||||
|
|
||||||
<p>The data subject may, as stated above, prevent the setting of cookies through our website at any time by means of a corresponding adjustment of the web browser used and thus permanently deny the setting of cookies. Such an adjustment to the used Internet browser would also prevent Matomo from setting a cookie on the information technology system of the data subject. In addition, cookies already in use by Matomo may be deleted at any time via a web browser or other software programs.</p>
|
|
||||||
|
|
||||||
<p>In addition, the data subject has the possibility of objecting to a collection of data relating to a use of this Internet site that are generated by Matomo as well as the processing of these data by Matomo and the chance to preclude any such. For this, the data subject must set a "Do Not Track" option in the browser.</p>
|
|
||||||
|
|
||||||
<p>With each setting of the opt-out cookie, however, there is the possibility that the websites of the controller are no longer fully usable for the data subject.</p>
|
|
||||||
|
|
||||||
<p>Further information and the applicable data protection provisions of Matomo may be retrieved under https://matomo.org/privacy/.</p>
|
|
||||||
|
|
||||||
<h4>9. Data protection provisions about the application and use of Twitter</h4>
|
|
||||||
<p>On this website, the controller has integrated components of Twitter. Twitter is a multilingual, publicly-accessible microblogging service on which users may publish and spread so-called ‘tweets,’ e.g. short messages, which are limited to 280 characters. These short messages are available for everyone, including those who are not logged on to Twitter. The tweets are also displayed to so-called followers of the respective user. Followers are other Twitter users who follow a user's tweets. Furthermore, Twitter allows you to address a wide audience via hashtags, links or retweets.</p>
|
|
||||||
|
|
||||||
<p>The operating company of Twitter is Twitter International Company, One Cumberland Place, Fenian Street Dublin 2, D02 AX07, Ireland.</p>
|
|
||||||
|
|
||||||
<p>With each call-up to one of the individual pages of this Internet site, which is operated by the controller and on which a Twitter component (Twitter button) was integrated, the Internet browser on the information technology system of the data subject is automatically prompted to download a display of the corresponding Twitter component of Twitter. Further information about the Twitter buttons is available under https://about.twitter.com/de/resources/buttons. During the course of this technical procedure, Twitter gains knowledge of what specific sub-page of our website was visited by the data subject. The purpose of the integration of the Twitter component is a retransmission of the contents of this website to allow our users to introduce this web page to the digital world and increase our visitor numbers.</p>
|
|
||||||
|
|
||||||
<p>If the data subject is logged in at the same time on Twitter, Twitter detects with every call-up to our website by the data subject and for the entire duration of their stay on our Internet site which specific sub-page of our Internet page was visited by the data subject. This information is collected through the Twitter component and associated with the respective Twitter account of the data subject. If the data subject clicks on one of the Twitter buttons integrated on our website, then Twitter assigns this information to the personal Twitter user account of the data subject and stores the personal data.</p>
|
|
||||||
|
|
||||||
<p>Twitter receives information via the Twitter component that the data subject has visited our website, provided that the data subject is logged in on Twitter at the time of the call-up to our website. This occurs regardless of whether the person clicks on the Twitter component or not. If such a transmission of information to Twitter is not desirable for the data subject, then he or she may prevent this by logging off from their Twitter account before a call-up to our website is made.</p>
|
|
||||||
|
|
||||||
<p>The applicable data protection provisions of Twitter may be accessed under https://twitter.com/privacy?lang=en.</p>
|
|
||||||
|
|
||||||
<h4>10. Legal basis for the processing </h4>
|
|
||||||
<p>Art. 6(1) lit. a GDPR serves as the legal basis for processing operations for which we obtain consent for a specific processing purpose. If the processing of personal data is necessary for the performance of a contract to which the data subject is party, as is the case, for example, when processing operations are necessary for the supply of goods or to provide any other service, the processing is based on Article 6(1) lit. b GDPR. The same applies to such processing operations which are necessary for carrying out pre-contractual measures, for example in the case of inquiries concerning our products or services. Is our company subject to a legal obligation by which processing of personal data is required, such as for the fulfillment of tax obligations, the processing is based on Art. 6(1) lit. c GDPR.
|
|
||||||
In rare cases, the processing of personal data may be necessary to protect the vital interests of the data subject or of another natural person. This would be the case, for example, if a visitor were injured in our company and his name, age, health insurance data or other vital information would have to be passed on to a doctor, hospital or other third party. Then the processing would be based on Art. 6(1) lit. d GDPR.
|
|
||||||
Finally, processing operations could be based on Article 6(1) lit. f GDPR. This legal basis is used for processing operations which are not covered by any of the abovementioned legal grounds, if processing is necessary for the purposes of the legitimate interests pursued by our company or by a third party, except where such interests are overridden by the interests or fundamental rights and freedoms of the data subject which require protection of personal data. Such processing operations are particularly permissible because they have been specifically mentioned by the European legislator. He considered that a legitimate interest could be assumed if the data subject is a client of the controller (Recital 47 Sentence 2 GDPR).
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h4>11. The legitimate interests pursued by the controller or by a third party</h4>
|
|
||||||
<p>Where the processing of personal data is based on Article 6(1) lit. f GDPR our legitimate interest is to carry out our business in favor of the well-being of all our employees and the shareholders.</p>
|
|
||||||
|
|
||||||
<h4>12. Period for which the personal data will be stored</h4>
|
|
||||||
<p>The criteria used to determine the period of storage of personal data is the respective statutory retention period. After expiration of that period, the corresponding data is routinely deleted, as long as it is no longer necessary for the fulfillment of the contract or the initiation of a contract.</p>
|
|
||||||
|
|
||||||
<h4>13. Provision of personal data as statutory or contractual requirement; Requirement necessary to enter into a contract; Obligation of the data subject to provide the personal data; possible consequences of failure to provide such data </h4>
|
|
||||||
<p>We clarify that the provision of personal data is partly required by law (e.g. tax regulations) or can also result from contractual provisions (e.g. information on the contractual partner).
|
|
||||||
|
|
||||||
Sometimes it may be necessary to conclude a contract that the data subject provides us with personal data, which must subsequently be processed by us. The data subject is, for example, obliged to provide us with personal data when our company signs a contract with him or her. The non-provision of the personal data would have the consequence that the contract with the data subject could not be concluded.
|
|
||||||
|
|
||||||
Before personal data is provided by the data subject, the data subject must contact any employee. The employee clarifies to the data subject whether the provision of the personal data is required by law or contract or is necessary for the conclusion of the contract, whether there is an obligation to provide the personal data and the consequences of non-provision of the personal data.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h4>14. Existence of automated decision-making</h4>
|
|
||||||
<p>As a responsible company, we do not use automatic decision-making or profiling.</p>
|
|
||||||
|
|
||||||
<p>Developed by the specialists for <a href="https://willing-able.com/">LegalTech</a> at Willing & Able that also developed the system for <a href="https://abletotrack.com/">Vacation schedule</a>. The legal texts contained in our privacy policy generator have been provided and published by <a href="https://dg-datenschutz.de/">Prof. Dr. h.c. Heiko Jonny Maniero</a> from the German Association for Data Protection and <a href="https://www.wbs-law.de/" rel="nofollow">Christian Solmecke</a> from WBS law.</p>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {setTitle} from "@/utils";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "PrivacyPolicy",
|
|
||||||
setup() {
|
|
||||||
setTitle('Privacy Policy')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
color: var(--bs-primary)
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="text-center mt-5">
|
|
||||||
<h1 class="pt-5">404</h1>
|
|
||||||
<h4 class="mt-4">The page you were looking for was not found!</h4>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: "ErrorPage404"
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="text-center mt-5">
|
|
||||||
<h1 class="pt-5">500</h1>
|
|
||||||
<h4 class="mt-4">An internal server error occurred!</h4>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: "ErrorPage500"
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="text-center mt-5">
|
|
||||||
<h1 class="pt-5">502</h1>
|
|
||||||
<h4 class="mt-4">You reached a bad gateway!</h4>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: "ErrorPage502"
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -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