updated frontend from typescript to javascript
@@ -9,8 +9,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.10.2",
|
||||
"axios": "^0.22.0",
|
||||
"bootstrap": "^5.1.1",
|
||||
"core-js": "^3.6.5",
|
||||
"luxon": "^2.0.2",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.0-0",
|
||||
"vuex": "^4.0.0-0"
|
||||
@@ -25,6 +27,8 @@
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^7.0.0",
|
||||
"sass-loader": "^12.1.0"
|
||||
"sass": "^1.42.1",
|
||||
"sass-loader": "^10.2.0",
|
||||
"string-sanitizer": "^2.0.2"
|
||||
}
|
||||
}
|
||||
|
98
pnpm-lock.yaml
generated
@@ -8,20 +8,26 @@ specifiers:
|
||||
'@vue/cli-plugin-vuex': ~4.5.0
|
||||
'@vue/cli-service': ~4.5.0
|
||||
'@vue/compiler-sfc': ^3.0.0
|
||||
axios: ^0.22.0
|
||||
babel-eslint: ^10.1.0
|
||||
bootstrap: ^5.1.1
|
||||
core-js: ^3.6.5
|
||||
eslint: ^6.7.2
|
||||
eslint-plugin-vue: ^7.0.0
|
||||
sass-loader: ^12.1.0
|
||||
luxon: ^2.0.2
|
||||
sass: ^1.42.1
|
||||
sass-loader: ^10.2.0
|
||||
string-sanitizer: ^2.0.2
|
||||
vue: ^3.0.0
|
||||
vue-router: ^4.0.0-0
|
||||
vuex: ^4.0.0-0
|
||||
|
||||
dependencies:
|
||||
'@popperjs/core': 2.10.2
|
||||
axios: 0.22.0
|
||||
bootstrap: 5.1.1_@popperjs+core@2.10.2
|
||||
core-js: 3.18.1
|
||||
luxon: 2.0.2
|
||||
vue: 3.2.19
|
||||
vue-router: 4.0.11_vue@3.2.19
|
||||
vuex: 4.0.2_vue@3.2.19
|
||||
@@ -31,12 +37,14 @@ devDependencies:
|
||||
'@vue/cli-plugin-eslint': 4.5.13_a58cf9e4d577795b8c257bee96d49483
|
||||
'@vue/cli-plugin-router': 4.5.13_@vue+cli-service@4.5.13
|
||||
'@vue/cli-plugin-vuex': 4.5.13_@vue+cli-service@4.5.13
|
||||
'@vue/cli-service': 4.5.13_0c7cfb9d6b60c37eed7038267d6bc444
|
||||
'@vue/cli-service': 4.5.13_14d4d8446413226c330137c0feac1b91
|
||||
'@vue/compiler-sfc': 3.2.19
|
||||
babel-eslint: 10.1.0_eslint@6.8.0
|
||||
eslint: 6.8.0
|
||||
eslint-plugin-vue: 7.18.0_eslint@6.8.0
|
||||
sass-loader: 12.1.0
|
||||
sass: 1.42.1
|
||||
sass-loader: 10.2.0_sass@1.42.1
|
||||
string-sanitizer: 2.0.2
|
||||
|
||||
packages:
|
||||
|
||||
@@ -1565,7 +1573,7 @@ packages:
|
||||
dependencies:
|
||||
'@babel/core': 7.15.5
|
||||
'@vue/babel-preset-app': 4.5.13_vue@3.2.19
|
||||
'@vue/cli-service': 4.5.13_0c7cfb9d6b60c37eed7038267d6bc444
|
||||
'@vue/cli-service': 4.5.13_14d4d8446413226c330137c0feac1b91
|
||||
'@vue/cli-shared-utils': 4.5.13
|
||||
babel-loader: 8.2.2_99877201e3f6dd5396b321f0a88244ea
|
||||
cache-loader: 4.1.0_webpack@4.46.0
|
||||
@@ -1584,7 +1592,7 @@ packages:
|
||||
'@vue/cli-service': ^3.0.0 || ^4.0.0-0
|
||||
eslint: '>= 1.6.0 < 7.0.0'
|
||||
dependencies:
|
||||
'@vue/cli-service': 4.5.13_0c7cfb9d6b60c37eed7038267d6bc444
|
||||
'@vue/cli-service': 4.5.13_14d4d8446413226c330137c0feac1b91
|
||||
'@vue/cli-shared-utils': 4.5.13
|
||||
eslint: 6.8.0
|
||||
eslint-loader: 2.2.1_eslint@6.8.0+webpack@4.46.0
|
||||
@@ -1602,7 +1610,7 @@ packages:
|
||||
peerDependencies:
|
||||
'@vue/cli-service': ^3.0.0 || ^4.0.0-0
|
||||
dependencies:
|
||||
'@vue/cli-service': 4.5.13_0c7cfb9d6b60c37eed7038267d6bc444
|
||||
'@vue/cli-service': 4.5.13_14d4d8446413226c330137c0feac1b91
|
||||
'@vue/cli-shared-utils': 4.5.13
|
||||
dev: true
|
||||
|
||||
@@ -1611,10 +1619,10 @@ packages:
|
||||
peerDependencies:
|
||||
'@vue/cli-service': ^3.0.0 || ^4.0.0-0
|
||||
dependencies:
|
||||
'@vue/cli-service': 4.5.13_0c7cfb9d6b60c37eed7038267d6bc444
|
||||
'@vue/cli-service': 4.5.13_14d4d8446413226c330137c0feac1b91
|
||||
dev: true
|
||||
|
||||
/@vue/cli-service/4.5.13_0c7cfb9d6b60c37eed7038267d6bc444:
|
||||
/@vue/cli-service/4.5.13_14d4d8446413226c330137c0feac1b91:
|
||||
resolution: {integrity: sha512-CKAZN4iokMMsaUyJRU22oUAz3oS/X9sVBSKAF2/shFBV5xh3jqAlKl8OXZYz4cXGFLA6djNuYrniuLAo7Ku97A==}
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
@@ -1687,7 +1695,7 @@ packages:
|
||||
pnp-webpack-plugin: 1.7.0
|
||||
portfinder: 1.0.28
|
||||
postcss-loader: 3.0.0
|
||||
sass-loader: 12.1.0
|
||||
sass-loader: 10.2.0_sass@1.42.1
|
||||
ssri: 8.0.1
|
||||
terser-webpack-plugin: 1.4.5_webpack@4.46.0
|
||||
thread-loader: 2.1.3_webpack@4.46.0
|
||||
@@ -2245,6 +2253,14 @@ packages:
|
||||
resolution: {integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==}
|
||||
dev: true
|
||||
|
||||
/axios/0.22.0:
|
||||
resolution: {integrity: sha512-Z0U3uhqQeg1oNcihswf4ZD57O3NrR1+ZXhxaROaWpDmsDTx7T2HNBV2ulBtie2hwJptu8UvgnJoK+BIqdzh/1w==}
|
||||
dependencies:
|
||||
follow-redirects: 1.14.4
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
dev: false
|
||||
|
||||
/babel-eslint/10.1.0_eslint@6.8.0:
|
||||
resolution: {integrity: sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -2378,7 +2394,6 @@ packages:
|
||||
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/bindings/1.5.0:
|
||||
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
|
||||
@@ -2761,7 +2776,6 @@ packages:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/chownr/1.1.4:
|
||||
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
||||
@@ -4280,7 +4294,7 @@ packages:
|
||||
readable-stream: 2.3.7
|
||||
dev: true
|
||||
|
||||
/follow-redirects/1.14.4_debug@4.3.2:
|
||||
/follow-redirects/1.14.4:
|
||||
resolution: {integrity: sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==}
|
||||
engines: {node: '>=4.0'}
|
||||
peerDependencies:
|
||||
@@ -4288,9 +4302,6 @@ packages:
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
dependencies:
|
||||
debug: 4.3.2
|
||||
dev: true
|
||||
|
||||
/for-in/1.0.2:
|
||||
resolution: {integrity: sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=}
|
||||
@@ -4807,7 +4818,7 @@ packages:
|
||||
engines: {node: '>=8.0.0'}
|
||||
dependencies:
|
||||
eventemitter3: 4.0.7
|
||||
follow-redirects: 1.14.4_debug@4.3.2
|
||||
follow-redirects: 1.14.4
|
||||
requires-port: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
@@ -5042,7 +5053,6 @@ packages:
|
||||
dependencies:
|
||||
binary-extensions: 2.2.0
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/is-boolean-object/1.1.2:
|
||||
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
|
||||
@@ -5535,7 +5545,6 @@ packages:
|
||||
emojis-list: 3.0.0
|
||||
json5: 2.2.0
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/locate-path/3.0.0:
|
||||
resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
|
||||
@@ -5613,6 +5622,17 @@ packages:
|
||||
yallist: 3.1.1
|
||||
dev: true
|
||||
|
||||
/lru-cache/6.0.0:
|
||||
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
yallist: 4.0.0
|
||||
dev: true
|
||||
|
||||
/luxon/2.0.2:
|
||||
resolution: {integrity: sha512-ZRioYLCgRHrtTORaZX1mx+jtxKtKuI5ZDvHNAmqpUzGqSrR+tL4FVLn/CUGMA3h0+AKD1MAxGI5GnCqR5txNqg==}
|
||||
dev: false
|
||||
|
||||
/magic-string/0.25.7:
|
||||
resolution: {integrity: sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==}
|
||||
dependencies:
|
||||
@@ -7078,7 +7098,6 @@ packages:
|
||||
dependencies:
|
||||
picomatch: 2.3.0
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/regenerate-unicode-properties/9.0.0:
|
||||
resolution: {integrity: sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==}
|
||||
@@ -7334,14 +7353,14 @@ packages:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
dev: true
|
||||
|
||||
/sass-loader/12.1.0:
|
||||
resolution: {integrity: sha512-FVJZ9kxVRYNZTIe2xhw93n3xJNYZADr+q69/s98l9nTCrWASo+DR2Ot0s5xTKQDDEosUkatsGeHxcH4QBp5bSg==}
|
||||
engines: {node: '>= 12.13.0'}
|
||||
/sass-loader/10.2.0_sass@1.42.1:
|
||||
resolution: {integrity: sha512-kUceLzC1gIHz0zNJPpqRsJyisWatGYNFRmv2CKZK2/ngMJgLqxTbXwe/hJ85luyvZkgqU3VlJ33UVF2T/0g6mw==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
peerDependencies:
|
||||
fibers: '>= 3.1.0'
|
||||
node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0
|
||||
sass: ^1.3.0
|
||||
webpack: ^5.0.0
|
||||
webpack: ^4.36.0 || ^5.0.0
|
||||
peerDependenciesMeta:
|
||||
fibers:
|
||||
optional: true
|
||||
@@ -7351,7 +7370,19 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
klona: 2.0.4
|
||||
loader-utils: 2.0.0
|
||||
neo-async: 2.6.2
|
||||
sass: 1.42.1
|
||||
schema-utils: 3.1.1
|
||||
semver: 7.3.5
|
||||
dev: true
|
||||
|
||||
/sass/1.42.1:
|
||||
resolution: {integrity: sha512-/zvGoN8B7dspKc5mC6HlaygyCBRvnyzzgD5khiaCfglWztY99cYoiTUksVx11NlnemrcfH5CEaCpsUKoW0cQqg==}
|
||||
engines: {node: '>=8.9.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
chokidar: 3.5.2
|
||||
dev: true
|
||||
|
||||
/sax/1.2.4:
|
||||
@@ -7376,6 +7407,15 @@ packages:
|
||||
ajv-keywords: 3.5.2_ajv@6.12.6
|
||||
dev: true
|
||||
|
||||
/schema-utils/3.1.1:
|
||||
resolution: {integrity: sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
dependencies:
|
||||
'@types/json-schema': 7.0.9
|
||||
ajv: 6.12.6
|
||||
ajv-keywords: 3.5.2_ajv@6.12.6
|
||||
dev: true
|
||||
|
||||
/select-hose/2.0.0:
|
||||
resolution: {integrity: sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=}
|
||||
dev: true
|
||||
@@ -7401,6 +7441,14 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/semver/7.3.5:
|
||||
resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
lru-cache: 6.0.0
|
||||
dev: true
|
||||
|
||||
/send/0.17.1:
|
||||
resolution: {integrity: sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -7792,6 +7840,10 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/string-sanitizer/2.0.2:
|
||||
resolution: {integrity: sha512-zECtWmUawolaVbUOdDRdhAM4jN7wl1sB4indjTmHpUFavzFSeYEDSVF85dZPPyDKoMRTJbrz+Tp0SjPPCWxscA==}
|
||||
dev: true
|
||||
|
||||
/string-width/2.1.1:
|
||||
resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==}
|
||||
engines: {node: '>=4'}
|
||||
|
21
scss/custom.scss
Normal file
@@ -0,0 +1,21 @@
|
||||
// Custom.scss
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');
|
||||
|
||||
// Default variable overrides
|
||||
$font-family-base: 'Roboto';
|
||||
//$primary: #292c34;
|
||||
//$secondary: #23262d;
|
||||
//$body-bg: #2e3139;
|
||||
//$blue: #4a90e2;
|
||||
$body-color: white;
|
||||
|
||||
$primary: #888f98;
|
||||
$secondary: #10121a;
|
||||
$body-bg: #1b2732;
|
||||
$blue: #5f7892;
|
||||
$warning: #c3a235;
|
||||
$info: $blue;
|
||||
$success: #609926;
|
||||
|
||||
// Bootstrap
|
||||
@import "../node_modules/bootstrap/scss/bootstrap";
|
26
src/App.vue
@@ -1,3 +1,27 @@
|
||||
<template>
|
||||
<router-view/>
|
||||
<header>
|
||||
<Nav/>
|
||||
</header>
|
||||
<main>
|
||||
<div class="spacer bg-secondary"></div>
|
||||
<router-view/>
|
||||
</main>
|
||||
<footer>
|
||||
<Footer />
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Nav from "@/components/Nav";
|
||||
import Footer from "@/components/Footer";
|
||||
|
||||
export default {
|
||||
components: {Footer, Nav},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.spacer {
|
||||
height: 70px;
|
||||
}
|
||||
</style>
|
31
src/components/Footer.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div class="bg-secondary text-center pt-5 pb-4">
|
||||
<div class="icons pb-4">
|
||||
<a class="text-white" target="_blank" href="https://git.harting.dev/CSGOWTF/csgowtf">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-github"
|
||||
viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="text">
|
||||
<p>This site is an open source project, originally created and maintained by <span class="text-warning">anonfunc</span> and <span
|
||||
class="text-warning">vikingowl</span>.</p>
|
||||
<p class="text-muted">For feedback contact us at <a class="text-muted text-decoration-none"
|
||||
href="mailto:#">EMAIL</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Footer",
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
p {
|
||||
font-size: .85rem;
|
||||
}
|
||||
</style>
|
110
src/components/Nav.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<nav class="navbar navbar-expand navbar-dark fixed-top">
|
||||
<div class="container-fluid w-75">
|
||||
<div class="navbar-nav">
|
||||
<router-link class="navbar-brand text-warning fw-bold fs-3" to="/">CSGO<span class="text-up text-white fw-bold">WTF</span>
|
||||
</router-link>
|
||||
<router-link class="nav-link" to="/explore">Explore</router-link>
|
||||
</div>
|
||||
<form class="d-flex col-5 justify-content-end" @keydown.enter.prevent="parseSearch">
|
||||
<label for="search">
|
||||
<svg class="bi bi-search" fill="currentColor" height="24" viewBox="0 0 16 16" width="24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
|
||||
</svg>
|
||||
</label>
|
||||
<input id="search" v-model="data.searchInput" aria-label="Search"
|
||||
class="form-control w-75 bg-transparent border-0"
|
||||
placeholder="SteamID64, Profile Link or Custom URL"
|
||||
type="search">
|
||||
</form>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {reactive} from "vue";
|
||||
import {useStore} from 'vuex'
|
||||
import {sanitize} from 'string-sanitizer'
|
||||
import router from "../router";
|
||||
|
||||
export default {
|
||||
name: 'Nav',
|
||||
setup() {
|
||||
const store = useStore()
|
||||
const data = reactive({
|
||||
searchInput: ''
|
||||
})
|
||||
|
||||
const parseSearch = () => {
|
||||
const input = data.searchInput
|
||||
const customUrlPattern = 'https://steamcommunity.com/id/'
|
||||
const profileUrlPattern = 'https://steamcommunity.com/profiles/'
|
||||
const id64Pattern = /^\d{17}$/
|
||||
|
||||
store.state.id64 = ''
|
||||
store.state.vanityUrl = ''
|
||||
|
||||
if (id64Pattern.test(input)) {
|
||||
store.state.id64 = input
|
||||
} else if (input.match(customUrlPattern)) {
|
||||
store.state.vanityUrl = sanitize(input.split('/')[4].split('?')[0])
|
||||
} else if (input.match(profileUrlPattern)) {
|
||||
const tmp = input.split('/')[4].split('?')[0]
|
||||
if (id64Pattern.test(tmp)) {
|
||||
store.state.id64 = tmp
|
||||
}
|
||||
} else {
|
||||
store.state.vanityUrl = sanitize(input)
|
||||
}
|
||||
|
||||
if (store.state.id64 !== '' || store.state.vanityUrl !== '') {
|
||||
data.searchInput = ''
|
||||
|
||||
if (store.state.id64) {
|
||||
router.push(`/player/${store.state.id64}`)
|
||||
} else if (store.state.vanityUrl) {
|
||||
router.push(`/player/${store.state.vanityUrl}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
data, parseSearch
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
nav {
|
||||
height: 70px;
|
||||
width: 100vw;
|
||||
background: rgba(16, 18, 26, 0.5);
|
||||
|
||||
.text-up {
|
||||
font-size: 40%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
label {
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
input[type="search"] {
|
||||
min-width: 300px;
|
||||
max-width: 300px;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 4px 2px -2px rgba(95, 120, 146, 0.59);
|
||||
transition: .2s ease-in-out;
|
||||
transform: scale(.975);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
71
src/components/ScoreTeam.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="player__avatar"></th>
|
||||
<th class="player__name"></th>
|
||||
<th class="player__rank"></th>
|
||||
<th class="player__kills cursor__help" title="Kills">K</th>
|
||||
<th class="player__assist cursor__help" title="Assists">A</th>
|
||||
<th class="player__deaths cursor__help" title="Deaths">D</th>
|
||||
<th class="player__diff cursor__help" title="Kill death difference">+/-</th>
|
||||
<th class="player__kd cursor__help" title="Kill death ratio">K/D</th>
|
||||
<th class="player__adr cursor__help" title="Average damage per round">ADR</th>
|
||||
<th class="player__hs cursor__help" title="Percentage of kills with a headshot">HS%</th>
|
||||
<th class="player__kast cursor__help" title="Percentage of rounds with a Kill, Assist, Survived or Death Traded">
|
||||
KAST
|
||||
</th>
|
||||
<th class="player__rating cursor__help" title="Estimated HLTV Rating 1.0">Rating</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody v-for="player in props.stats" :key="player.player.steamid64">
|
||||
<tr v-if="player.team_id === props.team_id" class="player">
|
||||
<ScoreTeamPlayer
|
||||
:assists="player.assists"
|
||||
:avatar="player.player.avatar"
|
||||
:deaths="player.deaths"
|
||||
:dmg="player.extended?.dmg?.enemy"
|
||||
:hs="player.headshot"
|
||||
:kast="player.extended?.kast"
|
||||
:kdiff="player.kills - player.deaths"
|
||||
:kills="player.kills"
|
||||
:mk_duo="player.extended?.multi_kills?.duo"
|
||||
:mk_pent="player.extended?.multi_kills?.pent"
|
||||
:mk_quad="player.extended?.multi_kills?.quad"
|
||||
:mk_triple="player.extended?.multi_kills?.triple"
|
||||
:name="player.player.name"
|
||||
:rounds_played="props.rounds_played"
|
||||
:rank="player.extended?.rank?.old"
|
||||
/>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ScoreTeamPlayer from '@/components/ScoreTeamPlayer.vue'
|
||||
|
||||
export default {
|
||||
name: 'ScoreTeam',
|
||||
components: {ScoreTeamPlayer},
|
||||
props: {
|
||||
stats: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
rounds_played: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
team_id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
return {props}
|
||||
}
|
||||
}
|
||||
</script>
|
130
src/components/ScoreTeamPlayer.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<td>
|
||||
<img :src="props.avatar" alt="Player avatar" class="player__avatar">
|
||||
</td>
|
||||
<td class="player__name">
|
||||
{{ props.name }}
|
||||
</td>
|
||||
<td>
|
||||
<img :src="props.rank ? require('@/images/ranks/' + props.rank + '.png') : require('@/images/ranks/0.png')"
|
||||
alt="Player rank"
|
||||
class="player__rank">
|
||||
</td>
|
||||
<td class="player__kills">
|
||||
{{ props.kills }}
|
||||
</td>
|
||||
<td class="player__assist">
|
||||
{{ props.assists }}
|
||||
</td>
|
||||
<td class="player__deaths">
|
||||
{{ props.deaths }}
|
||||
</td>
|
||||
<td :class="props.kdiff >= 0 ? 'text-success' : 'text-danger'" class="player__diff">
|
||||
{{ props.kdiff }}
|
||||
</td>
|
||||
<td class="player__kd">
|
||||
{{ (props.kills > 0 && props.deaths > 0) ? (props.kills / props.deaths).toFixed(2) : (props.kills > 0 && props.deaths === 0) ? props.kills : 0.00 }}
|
||||
</td>
|
||||
<td class="player__adr">
|
||||
{{ (props.dmg / props.rounds_played).toFixed(2) }}
|
||||
</td>
|
||||
<td class="player__hs">
|
||||
{{ (props.hs > 0 && props.kills > 0) ? (props.hs * 100 / props.kills).toFixed(0) + "%" : "0%" }}
|
||||
</td>
|
||||
<td class="player__kast">
|
||||
{{ props.kast ? props.kast + "%" : "-" }}
|
||||
</td>
|
||||
<td class="player__rating">
|
||||
{{
|
||||
GetHLTV_1(props.kills, props.rounds_played, props.deaths, props.mk_duo, props.mk_triple, props.mk_quad, props.mk_pent)
|
||||
}}
|
||||
</td>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {GetHLTV_1} from "../utils";
|
||||
|
||||
export default {
|
||||
name: 'ScoreTeamPlayer',
|
||||
props: {
|
||||
avatar: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: 'Avatar'
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: 'Name'
|
||||
},
|
||||
rank: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
kills: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
assists: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
deaths: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
kdiff: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
hs: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
rounds_played: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
mk_duo: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
mk_triple: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
mk_quad: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
mk_pent: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
kast: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
dmg: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return {props, GetHLTV_1}
|
||||
}
|
||||
}
|
||||
</script>
|
3
src/images/icons/gitfork.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M7,12 L14.5,12 C16.277025,12 17.7447372,10.6756742 17.970024,8.96013518 C16.2885152,8.7047201 15,7.25283448 15,5.5 C15,3.56700338 16.5670034,2 18.5,2 C20.4329966,2 22,3.56700338 22,5.5 C22,7.27155475 20.6838151,8.73569805 18.9759671,8.96790818 C18.7419236,11.2333126 16.8272778,13 14.5,13 L7,13 L7,15.0354444 C8.69614707,15.2780593 10,16.736764 10,18.5 C10,20.4329966 8.43299662,22 6.5,22 C4.56700338,22 3,20.4329966 3,18.5 C3,16.736764 4.30385293,15.2780593 6,15.0354444 L6,8.96455557 C4.30385293,8.72194074 3,7.26323595 3,5.5 C3,3.56700338 4.56700338,2 6.5,2 C8.43299662,2 10,3.56700338 10,5.5 C10,7.26323595 8.69614707,8.72194074 7,8.96455557 L7,12 Z M4,18.5 C4,19.8807119 5.11928813,21 6.5,21 C7.88071187,21 9,19.8807119 9,18.5 C9,17.1192881 7.88071187,16 6.5,16 C5.11928813,16 4,17.1192881 4,18.5 Z M4,5.5 C4,6.88071187 5.11928813,8 6.5,8 C7.88071187,8 9,6.88071187 9,5.5 C9,4.11928813 7.88071187,3 6.5,3 C5.11928813,3 4,4.11928813 4,5.5 Z M18.5,3 C17.1192881,3 16,4.11928813 16,5.5 C16,6.88071187 17.1192881,8 18.5,8 C19.8807119,8 21,6.88071187 21,5.5 C21,4.11928813 19.8807119,3 18.5,3 Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/images/maps/de_inferno.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
src/images/maps/de_mirage.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/images/maps/de_overpass.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/images/maps/not_found.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
src/images/ranks/0.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/images/ranks/1.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/images/ranks/10.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
src/images/ranks/11.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
src/images/ranks/12.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
src/images/ranks/13.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
src/images/ranks/14.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
src/images/ranks/15.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/images/ranks/16.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
src/images/ranks/17.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
src/images/ranks/18.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
src/images/ranks/2.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
src/images/ranks/3.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
src/images/ranks/4.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
src/images/ranks/5.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
src/images/ranks/6.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
src/images/ranks/7.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
src/images/ranks/8.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
src/images/ranks/9.png
Normal file
After Width: | Height: | Size: 23 KiB |
@@ -2,5 +2,7 @@ import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
import 'bootstrap'
|
||||
import '../scss/custom.scss'
|
||||
|
||||
createApp(App).use(store).use(router).mount('#app')
|
||||
|
@@ -1,12 +1,43 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import Home from '../views/Home.vue'
|
||||
|
||||
function lazyLoad(view) {
|
||||
return () => import(`@/views/${view}.vue`)
|
||||
}
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: Home
|
||||
component: lazyLoad('Home')
|
||||
},
|
||||
{
|
||||
path: '/player/:id',
|
||||
name: 'Player',
|
||||
component: lazyLoad('Player'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/player/:id/matches',
|
||||
name: 'MyMatches',
|
||||
component: lazyLoad('MyMatches'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/match/:match_id',
|
||||
name: 'Match',
|
||||
component: lazyLoad('Match'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/explore',
|
||||
name: 'Explore',
|
||||
component: lazyLoad('Explore'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
redirect: '/'
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
|
@@ -2,6 +2,8 @@ import { createStore } from 'vuex'
|
||||
|
||||
export default createStore({
|
||||
state: {
|
||||
id64: '',
|
||||
vanityUrl: ''
|
||||
},
|
||||
mutations: {
|
||||
},
|
||||
|
50
src/utils/index.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import {DateTime} from "luxon/build/es6/luxon";
|
||||
import router from '@/router'
|
||||
|
||||
export const FormatDuration = (d) => {
|
||||
const hours = Math.floor(d / 3600)
|
||||
const num = d % 3600
|
||||
const minutes = Math.floor(num % 3600 / 60)
|
||||
const seconds = Math.floor(num % 3600 % 60)
|
||||
|
||||
if (hours !== 0)
|
||||
return `${hours}:${minutes < 10 ? '0' + minutes : minutes}:${seconds < 10 ? '0' + seconds : seconds}`
|
||||
else
|
||||
return `${minutes < 10 ? '0' + minutes : minutes}:${seconds < 10 ? '0' + seconds : seconds}`
|
||||
}
|
||||
|
||||
export const FormatDate = (date) => {
|
||||
const matchDate = DateTime.fromISO(date)
|
||||
const diff = DateTime.now().diff(matchDate)
|
||||
|
||||
if (diff.as('days') > 10)
|
||||
return matchDate.toLocaleString({weekday: 'short', day: 'numeric', month: 'numeric', year: 'numeric'})
|
||||
else if (diff.as('days') < 1)
|
||||
if (diff.as('hours') < 1)
|
||||
return Math.floor(diff.as('minutes')) + ' minutes ago'
|
||||
else
|
||||
return Math.floor(diff.as('hours')) + ' hours ago'
|
||||
else
|
||||
return Math.floor(diff.as('days')) + ' days ago'
|
||||
}
|
||||
|
||||
export const GoToMatch = (id) => {
|
||||
router.push(`/match/${id}`)
|
||||
}
|
||||
|
||||
export const GoToPlayer = (id) => {
|
||||
router.push(`/player/${id}`)
|
||||
}
|
||||
|
||||
export const GetHLTV_1 = (kills = 0, rounds, deaths = 0, k2 = 0, k3 = 0, k4 = 0, k5 = 0) => {
|
||||
const k1 = kills - k2 - k3 - k4 - k5
|
||||
const Weight_KPR = 0.679 // weight kills per round
|
||||
const Weight_SPR = 0.317 // weight survived rounds per round
|
||||
const Weight_RMK = 1.277 // weight value calculated from rounds with multiple kills (1k + 4*2k + 9*3k + 16*4k + 25*5k)
|
||||
|
||||
const KillRating = kills / rounds / Weight_KPR
|
||||
const SurvivalRating = (rounds - deaths) / rounds / Weight_SPR
|
||||
const RoundsWithMultipleKillsRating = (k1 + 4 * k2 + 9 * k3 + 16 * k4 + 25 * k5) / rounds / Weight_RMK
|
||||
|
||||
return ((KillRating + 0.7 * SurvivalRating + RoundsWithMultipleKillsRating) / 2.7).toFixed(2)
|
||||
}
|
9
src/views/Explore.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
Explore
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Explore'
|
||||
}
|
||||
</script>
|
@@ -1,3 +1,112 @@
|
||||
<template>
|
||||
Home
|
||||
<div class="main-content content text-center">
|
||||
<div class="head">
|
||||
<h1 class="text-warning fw-bold">CSGO<span class="text-up text-white">WTF</span></h1>
|
||||
<h3>Open source CSGO data platform</h3>
|
||||
</div>
|
||||
<div class="body row m-auto mt-5 mb-5">
|
||||
<div class="col-4">
|
||||
<svg class="mb-4" height="80" viewBox="0 0 24 24" width="80" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7,12 L14.5,12 C16.277025,12 17.7447372,10.6756742 17.970024,8.96013518 C16.2885152,8.7047201 15,7.25283448 15,5.5 C15,3.56700338 16.5670034,2 18.5,2 C20.4329966,2 22,3.56700338 22,5.5 C22,7.27155475 20.6838151,8.73569805 18.9759671,8.96790818 C18.7419236,11.2333126 16.8272778,13 14.5,13 L7,13 L7,15.0354444 C8.69614707,15.2780593 10,16.736764 10,18.5 C10,20.4329966 8.43299662,22 6.5,22 C4.56700338,22 3,20.4329966 3,18.5 C3,16.736764 4.30385293,15.2780593 6,15.0354444 L6,8.96455557 C4.30385293,8.72194074 3,7.26323595 3,5.5 C3,3.56700338 4.56700338,2 6.5,2 C8.43299662,2 10,3.56700338 10,5.5 C10,7.26323595 8.69614707,8.72194074 7,8.96455557 L7,12 Z M4,18.5 C4,19.8807119 5.11928813,21 6.5,21 C7.88071187,21 9,19.8807119 9,18.5 C9,17.1192881 7.88071187,16 6.5,16 C5.11928813,16 4,17.1192881 4,18.5 Z M4,5.5 C4,6.88071187 5.11928813,8 6.5,8 C7.88071187,8 9,6.88071187 9,5.5 C9,4.11928813 7.88071187,3 6.5,3 C5.11928813,3 4,4.11928813 4,5.5 Z M18.5,3 C17.1192881,3 16,4.11928813 16,5.5 C16,6.88071187 17.1192881,8 18.5,8 C19.8807119,8 21,6.88071187 21,5.5 C21,4.11928813 19.8807119,3 18.5,3 Z"
|
||||
fill="white"/>
|
||||
</svg>
|
||||
<h4 class="fw-light">Open Source</h4>
|
||||
<p class="w-75 m-auto fw-light">All project code is open source and available for contributors to improve and
|
||||
modify.</p>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<svg class="bi bi-bar-chart-fill mb-4" fill="currentColor" height="80" viewBox="0 0 16 16"
|
||||
width="80" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M1 11a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-3zm5-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7zm5-5a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V2z"/>
|
||||
</svg>
|
||||
<h4 class="fw-light">In-Depth Data</h4>
|
||||
<p class="w-75 m-auto fw-light">Parsing replay files provides highly detailed match data.</p>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<svg class="bi bi-stars mb-4" fill="currentColor" height="80" viewBox="0 0 16 16" width="80"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M7.657 6.247c.11-.33.576-.33.686 0l.645 1.937a2.89 2.89 0 0 0 1.829 1.828l1.936.645c.33.11.33.576 0 .686l-1.937.645a2.89 2.89 0 0 0-1.828 1.829l-.645 1.936a.361.361 0 0 1-.686 0l-.645-1.937a2.89 2.89 0 0 0-1.828-1.828l-1.937-.645a.361.361 0 0 1 0-.686l1.937-.645a2.89 2.89 0 0 0 1.828-1.828l.645-1.937zM3.794 1.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387A1.734 1.734 0 0 0 4.593 5.69l-.387 1.162a.217.217 0 0 1-.412 0L3.407 5.69A1.734 1.734 0 0 0 2.31 4.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387A1.734 1.734 0 0 0 3.407 2.31l.387-1.162zM10.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732L9.1 2.137a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L10.863.1z"/>
|
||||
</svg>
|
||||
<h4 class="fw-light">Free of Charge</h4>
|
||||
<p class="w-75 m-auto fw-light">This service is free of charge. If you want to support us, just contact us.</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="m-auto text-muted">
|
||||
<div class="foot">
|
||||
MORE TEXT
|
||||
</div>
|
||||
<span class="image-by">Image by <a class="text-decoration-none text-info" href="https://unsplash.com/photos/6ou8gWpS9ns"
|
||||
target="_blank">Fredrick Tendong</a></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {onMounted} from "vue";
|
||||
|
||||
export default {
|
||||
name: 'Home',
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
document.title = 'Home | CSGO-W.TV'
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
.main-content {
|
||||
.head {
|
||||
background-image: url("https://images.unsplash.com/photo-1560419015-7c427e8ae5ba?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1470&q=80");
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
|
||||
padding-top: 10rem;
|
||||
padding-bottom: 10rem;
|
||||
|
||||
h1 {
|
||||
font-size: 5rem;
|
||||
|
||||
&:before {
|
||||
content: 'CSGO';
|
||||
position: absolute;
|
||||
text-shadow: 0 0 1rem rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.text-up {
|
||||
font-size: 40%;
|
||||
vertical-align: top;
|
||||
text-shadow: 10px -5px 1rem rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: lighter;
|
||||
}
|
||||
}
|
||||
|
||||
.body, hr {
|
||||
width: 65%;
|
||||
|
||||
p {
|
||||
font-size: .9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.foot {
|
||||
margin-top: 5rem;
|
||||
padding-bottom: 5rem;
|
||||
}
|
||||
|
||||
.image-by {
|
||||
position: absolute;
|
||||
bottom: -35px;
|
||||
right: 2rem;
|
||||
font-size: .8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
177
src/views/Match.vue
Normal file
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div class="pt-3">
|
||||
<p class="text-center fs-6">Average Rank: <img :src="require('@/images/ranks/' + data.avgRank + '.png')"
|
||||
alt="Rank icon"
|
||||
class="rank-icon mx-2"/></p>
|
||||
<div v-if="data.matchDetails" class="head row m-auto mb-3 text-center">
|
||||
<div class="m-auto">
|
||||
<img :alt="data.matchDetails.map"
|
||||
:src="mapImg.includes(data.matchDetails.map) ? require('@/images/maps/' + data.matchDetails.map + '.png') : require('@/images/maps/not_found.png')"
|
||||
:title="data.matchDetails.map" class="map-icon">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav">
|
||||
<ul class="list-unstyled d-flex m-auto">
|
||||
<li class="list-item active">Scoreboard</li>
|
||||
<li class="list-item">Rounds</li>
|
||||
<li class="list-item">Weapons</li>
|
||||
<li class="list-item">Duels</li>
|
||||
<li class="list-item">Heatmaps</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="container row m-auto">
|
||||
<div class="score col-3 flex-column">
|
||||
{{data.score[0]}}
|
||||
-
|
||||
{{data.score[1]}}
|
||||
</div>
|
||||
<div v-if="data.score.length === 2 && data.stats" class="col-9 d-flex flex-column">
|
||||
<ScoreTeam :rounds_played="data.score.reduce((a, b) => a + b)" :stats="data.stats" :team_id="1"/>
|
||||
<ScoreTeam :rounds_played="data.score.reduce((a, b) => a + b)" :stats="data.stats" :team_id="2"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {defineAsyncComponent, onBeforeMount, reactive, watch} from "vue";
|
||||
import axios from 'axios'
|
||||
import {GetHLTV_1, GoToPlayer} from "../utils";
|
||||
import "../components/ScoreTeam"
|
||||
|
||||
const ScoreTeam = defineAsyncComponent(() => import('../components/ScoreTeam'))
|
||||
|
||||
export default {
|
||||
name: 'Match',
|
||||
props: ['match_id'],
|
||||
components: {ScoreTeam},
|
||||
setup(props) {
|
||||
// Vars
|
||||
const mapImg = ['de_inferno', 'de_mirage', 'de_overpass']
|
||||
|
||||
// Refs
|
||||
const data = reactive({
|
||||
playerName: '',
|
||||
matchDetails: {},
|
||||
stats: [],
|
||||
score: [0],
|
||||
avgRank: 0,
|
||||
})
|
||||
|
||||
// Functions
|
||||
const GetMatch = () => {
|
||||
axios
|
||||
.get(`http://localhost:1337/http://localhost:8000/match/${props.match_id}`)
|
||||
.then((response) => {
|
||||
document.title = `${response.data.map} | CSGO-W.TF`
|
||||
data.matchDetails = response.data
|
||||
data.stats = response.data.stats
|
||||
data.score = response.data.score
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
})
|
||||
}
|
||||
|
||||
const GetAvgRank = () => {
|
||||
let count = 0
|
||||
let fullRank = 0
|
||||
|
||||
data.stats?.map(player => {
|
||||
if (player.extended?.rank?.old) {
|
||||
fullRank += player.extended?.rank?.old
|
||||
count += 1
|
||||
}
|
||||
})
|
||||
|
||||
if (count === 0)
|
||||
data.avgRank = 0
|
||||
else
|
||||
data.avgRank = Math.floor(fullRank / count)
|
||||
}
|
||||
|
||||
// Watchers
|
||||
watch(() => props.match_id, GetMatch)
|
||||
watch(() => data.stats, GetAvgRank)
|
||||
|
||||
// Run on create
|
||||
onBeforeMount(() => {
|
||||
GetMatch()
|
||||
})
|
||||
|
||||
return {
|
||||
GetMatch, GetAvgRank, data, mapImg, GoToPlayer, GetHLTV_1
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.rank-icon {
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
.map-icon {
|
||||
max-width: 60px;
|
||||
}
|
||||
|
||||
.score {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 900px;
|
||||
text-align: center;
|
||||
|
||||
tr {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.cursor__help {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.player__avatar {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.player__name {
|
||||
text-align: left;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.player__rank {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.player__rating {
|
||||
border-radius: 25% 25%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.nav {
|
||||
width: 100vw;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
|
||||
.list-item {
|
||||
padding: 10px 10px;
|
||||
|
||||
&:hover {
|
||||
background: var(--bs-info);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.active {
|
||||
background: var(--bs-info)
|
||||
}
|
||||
}
|
||||
</style>
|
12
src/views/MyMatches.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<table class="matches-table">
|
||||
MyMatches
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default{
|
||||
name: "MyMatches",
|
||||
}
|
||||
</script>
|
317
src/views/Player.vue
Normal file
@@ -0,0 +1,317 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<div class="container">
|
||||
<div class="card mb-3 bg-transparent border-0" style="max-width: 540px;">
|
||||
<div class="row g-0">
|
||||
<div class="img-container col-md-4 pt-3">
|
||||
<img
|
||||
:src="data.playerDetails.avatar" alt="Player avatar" class="img-fluid avatar">
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title"><a
|
||||
:href="/^\d{17}$/.test(props.id) ? 'https://steamcommunity.com/profiles/' + props.id : 'https://steamcommunity.com/id/' + props.id"
|
||||
class="text-decoration-none text-white"
|
||||
target="_blank"
|
||||
title="Open steam profile">{{
|
||||
data.playerDetails.name
|
||||
}}
|
||||
<svg class="bi bi-link-45deg" fill="currentColor" height="16" viewBox="0 0 16 16"
|
||||
width="16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1.002 1.002 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4.018 4.018 0 0 1-.128-1.287z"/>
|
||||
<path
|
||||
d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243L6.586 4.672z"/>
|
||||
</svg>
|
||||
</a></h3>
|
||||
<table class="table table-borderless">
|
||||
<tr>
|
||||
<th class="text-uppercase text-muted">Wins</th>
|
||||
<th class="text-uppercase text-muted">Losses</th>
|
||||
<th class="text-uppercase text-muted">WinRate</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ wl.win }}</td>
|
||||
<td>{{ wl.loss }}</td>
|
||||
<td>{{ (wl.win / wl.loss * 100).toFixed(2) }}%</td>
|
||||
</tr>
|
||||
</table>
|
||||
<small v-if="data.playerDetails.tracked" class="card-text text-muted">
|
||||
Currently tracked
|
||||
</small>
|
||||
<div v-else class="dropdown">
|
||||
<button
|
||||
id="login-dropdown"
|
||||
aria-expanded="false"
|
||||
class="btn border-2 btn-outline-info"
|
||||
data-bs-toggle="dropdown"
|
||||
type="button"
|
||||
>
|
||||
Track Me!
|
||||
</button>
|
||||
<div aria-labelledby="login-dropdown" class="dropdown-menu mt-2 border-2 border-primary bg-body"
|
||||
style="width: 320px">
|
||||
<form class="px-4 py-3">
|
||||
<!-- AuthCode input -->
|
||||
<div class="form-outline mb-4">
|
||||
<input id="track-authcode" v-model="data.userData.authcode" class="form-control bg-secondary"
|
||||
placeholder="AuthCode*"
|
||||
required type="text"/>
|
||||
</div>
|
||||
|
||||
<!-- ShareCode input -->
|
||||
<div class="form-outline mb-2">
|
||||
<input id="track-sharecode" v-model="data.userData.sharecode" class="form-control bg-secondary"
|
||||
placeholder="ShareCode"
|
||||
type="text"/>
|
||||
</div>
|
||||
|
||||
<div class="form-outline mb-4">
|
||||
<small>You can find your AuthCode and ShareCode <a
|
||||
href="https://help.steampowered.com/en/wizard/HelpWithGameIssue/?appid=730&issueid=128" target="_blank">here</a>.</small>
|
||||
</div>
|
||||
|
||||
<!-- Submit button -->
|
||||
<button class="btn btn-outline-warning border-2" type="submit" @click.prevent="TrackMe">
|
||||
TrackMe
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="matches m-auto">
|
||||
<table v-if="data.matches" class="table table-borderless">
|
||||
<thead class="border-bottom">
|
||||
<tr>
|
||||
<th class="text-center" scope="col">Map</th>
|
||||
<th class="text-center" scope="col">Rank</th>
|
||||
<th class="text-center" scope="col">Score</th>
|
||||
<th class="text-center" scope="col">K</th>
|
||||
<th class="text-center" scope="col">A</th>
|
||||
<th class="text-center" scope="col">D</th>
|
||||
<th class="text-center" scope="col" style="cursor: help" title="Kill-to-death ratio">+/-</th>
|
||||
<th class="text-center" scope="col" style="cursor: help" title="Average damage per round">ADR</th>
|
||||
<th class="text-center" scope="col" style="cursor: help" title="HLTV 1.0 Rating">Rating</th>
|
||||
<th class="text-center" scope="col">Duration</th>
|
||||
<th scope="col">Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="match in data.matches"
|
||||
:key="match.match_id"
|
||||
class="match"
|
||||
@click="GoToMatch(match.match_id)">
|
||||
<td class="td-map text-center">
|
||||
<img :alt="match.map ? match.map : 'Map not found'"
|
||||
:src="mapImg.includes(match.map) ? require('@/images/maps/' + match.map + '.png') : require('@/images/maps/not_found.png')"
|
||||
:title="match.map"
|
||||
class="map-icon">
|
||||
</td>
|
||||
<td class="td-rank text-center">
|
||||
<img
|
||||
:src="match.stats[0].extended?.rank?.new ? require('@/images/ranks/' + match.stats[0].extended?.rank?.new + '.png') : require('@/images/ranks/0.png')"
|
||||
alt="Rank icon"
|
||||
class="rank-icon">
|
||||
</td>
|
||||
<td :class="match.stats[0].team_id === match.match_result ? 'text-success' : !match.match_result ? 'text-warning' : 'text-danger'"
|
||||
class="td-score text-center fw-bold">
|
||||
{{ match.score[0] }} - {{ match.score[1] }}
|
||||
</td>
|
||||
<td class="td-kills text-center">
|
||||
{{ match.stats[0].kills ? match.stats[0].kills : "0" }}
|
||||
</td>
|
||||
<td class="td-assists text-center">
|
||||
{{ match.stats[0].assists ? match.stats[0].assists : "0" }}
|
||||
</td>
|
||||
<td class="td-deaths text-center">
|
||||
{{ match.stats[0].deaths ? match.stats[0].deaths : "0" }}
|
||||
</td>
|
||||
<td :class="(match.stats[0].kills ? match.stats[0].kills : 0) - (match.stats[0].deaths ? match.stats[0].deaths : 0) >= 0 ? 'text-success' : 'text-danger'"
|
||||
class="td-plus text-center">
|
||||
{{
|
||||
(match.stats[0].kills ? match.stats[0].kills : 0) - (match.stats[0].deaths ? match.stats[0].deaths : 0)
|
||||
}}
|
||||
</td>
|
||||
<td class="td-adr text-center">
|
||||
{{
|
||||
Math.floor((match.stats[0].extended.dmg.total ? match.stats[0].extended.dmg.total : 0) / (match.score[0] + match.score[1]))
|
||||
}}
|
||||
</td>
|
||||
<td :class="GetHLTV_1(
|
||||
match.stats[0].kills,
|
||||
match.score[0] + match.score[1],
|
||||
match.stats[0].deaths,
|
||||
match.stats[0].extended?.multi_kills?.duo,
|
||||
match.stats[0].extended?.multi_kills?.triple,
|
||||
match.stats[0].extended?.multi_kills?.quad,
|
||||
match.stats[0].extended?.multi_kills?.pent) >= 1 ? 'text-success' : 'text-warning'"
|
||||
class="td-hltv text-center fw-bold">
|
||||
{{
|
||||
GetHLTV_1(
|
||||
match.stats[0].kills,
|
||||
match.score[0] + match.score[1],
|
||||
match.stats[0].deaths,
|
||||
match.stats[0].extended?.multi_kills?.duo,
|
||||
match.stats[0].extended?.multi_kills?.triple,
|
||||
match.stats[0].extended?.multi_kills?.quad,
|
||||
match.stats[0].extended?.multi_kills?.pent)
|
||||
}}
|
||||
</td>
|
||||
<td class="td-duration text-center">
|
||||
{{ FormatDuration(match.duration) }}
|
||||
</td>
|
||||
<td class="td-date">
|
||||
{{ FormatDate(match.date) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h5 v-else>No matches on record</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {onBeforeMount, reactive, watch} from "vue";
|
||||
import axios from "axios";
|
||||
import {useStore} from "vuex";
|
||||
import {FormatDate, FormatDuration, GetHLTV_1, GoToMatch} from "@/utils";
|
||||
|
||||
export default {
|
||||
name: 'Player',
|
||||
props: ['id'],
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
|
||||
// Vars
|
||||
const mapImg = ['de_inferno', 'de_mirage', 'de_overpass']
|
||||
|
||||
const wl = reactive({
|
||||
win: 132,
|
||||
loss: 102
|
||||
})
|
||||
|
||||
const data = reactive({
|
||||
userData: {
|
||||
authcode: '',
|
||||
sharecode: ''
|
||||
},
|
||||
playerDetails: {},
|
||||
matches: [],
|
||||
statusError: ''
|
||||
})
|
||||
|
||||
// Functions
|
||||
const TrackMe = async () => {
|
||||
const shareCodeRegex = /^CSGO(?:-?[ABCDEFGHJKLMNOPQRSTUVWXYZabcdefhijkmnopqrstuvwxyz23456789]{5}){5}$/
|
||||
const authCodeRegex = /^[ABCDEFGHJKLMNOPQRSTUVWXYZ23456789]{4}-[ABCDEFGHJKLMNOPQRSTUVWXYZ23456789]{5}-[ABCDEFGHJKLMNOPQRSTUVWXYZ23456789]{4}$/
|
||||
|
||||
if (!shareCodeRegex.test(data.userData.sharecode))
|
||||
data.statusError = 'Is not a valid sharecode'
|
||||
if (!authCodeRegex.test(data.userData.authcode))
|
||||
data.statusError = 'Is not a valid authcode'
|
||||
|
||||
const res = await axios
|
||||
.post(`http://localhost:1337/http://localhost:8000/player/trackme`, `id=${store.state.id64}&authcode=${data.userData.authcode}&sharecode=${data.userData.sharecode}`)
|
||||
|
||||
if (res.status === 401) {
|
||||
data.statusError = 'Data does not match player'
|
||||
} else if (res.status === 400) {
|
||||
data.statusError = 'Userinput was wrong'
|
||||
} else if (res.status === 200) {
|
||||
data.statusError = ''
|
||||
} else {
|
||||
console.log(`${res.status}: ${res.statusText}`)
|
||||
}
|
||||
}
|
||||
|
||||
const GetUser = () => {
|
||||
axios
|
||||
.get(`http://localhost:1337/http://localhost:8000/player/${props.id}`)
|
||||
.then((response) => {
|
||||
data.playerDetails = response.data
|
||||
data.matches = response.data.matches
|
||||
store.state.id64 = response.data.steamid64
|
||||
|
||||
console.log(response.data)
|
||||
document.title = `${response.data.name} | CSGO-W.TF`
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
}
|
||||
|
||||
watch(() => props.id, GetUser)
|
||||
|
||||
onBeforeMount(() => {
|
||||
GetUser()
|
||||
})
|
||||
|
||||
return {
|
||||
data, store, mapImg, wl, props, GetUser, TrackMe, FormatDate, FormatDuration, GoToMatch, GetHLTV_1
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
th:last-child, td:last-child {
|
||||
text-align: right;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.td-map {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.td-rank {
|
||||
width: 88px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.td-score {
|
||||
font-size: 1.2rem;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border-radius: 50%;
|
||||
height: 150px;
|
||||
width: 150px;
|
||||
box-shadow: 0 0 10px black;
|
||||
}
|
||||
|
||||
.map-icon {
|
||||
height: 75px;
|
||||
}
|
||||
|
||||
.rank-icon {
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.match {
|
||||
border-bottom: 1px solid rgba(73, 73, 73, 0.73);
|
||||
}
|
||||
|
||||
.match:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.match:hover {
|
||||
cursor: pointer;
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
</style>
|