99 Commits
Author SHA1 Message Date
legonzaur 9f596b9f50 'publish' 2023-06-01 10:36:49 +02:00
legonzaur 0dbcadf0cf Merge branch 'master' of https://github.com/ToneAPI/ToneAPI_webclient 2023-06-01 10:29:22 +02:00
legonzaur 1e28a11007 Add lazy loading 2023-06-01 10:28:04 +02:00
legonzaur e1394ce66c Remove dist 2023-06-01 10:27:44 +02:00
legonzaur e489783022 'publish' 2023-05-31 13:27:38 +02:00
legonzaur 5d330970c1 fix list id width 2023-05-31 13:26:58 +02:00
legonzaur 097afd085f 'publish' 2023-05-26 16:04:08 +02:00
legonzaur 78e3c6864b remove consle logs 2023-05-26 16:03:51 +02:00
legonzaur 46a94b7898 Fix infinite loading for weapon chart 2023-05-26 16:03:01 +02:00
legonzaur 72105b3a4c 'publish' 2023-05-26 15:42:12 +02:00
legonzaur 9764f65cf6 add progress bar 2023-05-26 15:41:49 +02:00
legonzaur c565d1593b fix negative index count if list is too small 2023-05-26 13:18:33 +02:00
legonzaur 9446d7ec15 'publish' 2023-05-16 11:59:13 +02:00
legonzaur fb6684820b rename alternator image 2023-05-16 11:58:47 +02:00
legonzaur 41b6ea08f9 'publish' 2023-05-15 15:57:27 +02:00
legonzaur 388d9877b0 add weapons 2023-05-15 15:57:10 +02:00
legonzaur 727a1d8e11 'publish' 2023-05-15 10:57:58 +02:00
legonzaur 8e8408b2f8 fix players having 500 kills out of nowhere 2023-05-15 10:57:36 +02:00
legonzaur 14110c89e4 'publish' 2023-05-06 13:13:04 +02:00
legonzaur 76db4c262d change order of elements in server list 2023-05-06 13:12:48 +02:00
legonzaur 6c7a502785 'publish' 2023-05-06 13:03:32 +02:00
legonzaur e48ae26c51 fix empty space being taken if no image 2023-05-06 13:03:17 +02:00
legonzaur ffd0db46d9 'publish' 2023-05-06 12:59:15 +02:00
legonzaur 55312a19ff add meadow image 2023-05-06 12:59:00 +02:00
legonzaur a97e088a52 'publish' 2023-05-06 12:46:22 +02:00
legonzaur b4d9749025 Add caps to headers 2023-05-06 12:45:04 +02:00
legonzaur 71698cd31c Layout fixes for server view 2023-05-06 12:44:59 +02:00
legonzaur 54aaff2f74 remove links 2023-05-06 12:44:43 +02:00
legonzaur 0d6cb316a3 update og image 2023-05-06 12:44:36 +02:00
legonzaur 5d5a39ab2a update og _image 2023-05-06 12:34:31 +02:00
legonzaur a8aa64d18b fix playerList seek 2023-05-06 12:12:03 +02:00
legonzaur deb9db2704 fix list color 2023-05-06 11:44:05 +02:00
legonzaur 9bce40819d work on server list 2023-05-06 11:35:55 +02:00
legonzaur b1a43aa7d4 Sort servers by activity 2023-05-06 09:05:29 +02:00
legonzaur 155b8a1946 add relic 2023-05-06 09:04:12 +02:00
legonzaur ef7806da03 Merge branch 'master' into server 2023-05-06 09:01:32 +02:00
legonzaur f3b6779641 change title for embed 2023-05-05 21:56:34 +02:00
legonzaur 1eef943a0f 'publish' 2023-05-05 21:51:02 +02:00
legonzaur cf2dba3648 'publish' 2023-05-05 21:42:10 +02:00
legonzaur 52c45e53b8 fix for infinite redirection 2023-05-05 21:41:46 +02:00
legonzaur 224880dfc0 'publish' 2023-05-05 21:39:25 +02:00
legonzaur bf37a31802 add server preview 2023-05-05 21:39:04 +02:00
legonzaur 1c583122f8 add colony image 2023-05-05 21:29:37 +02:00
legonzaur 32ccc5cb39 prototype of server list 2023-05-05 21:23:18 +02:00
legonzaur c95590931f changed virtualList to be modulable 2023-05-05 21:00:29 +02:00
legonzaur 341f81a6a5 'publish' 2023-05-05 20:47:59 +02:00
legonzaur 2f5bbca42c fix server filter not applying for weapon object in realtime 2023-05-05 20:47:43 +02:00
legonzaur 0cbd5a0ef2 'publish' 2023-05-05 20:45:47 +02:00
legonzaur fdcfff2554 fix image path 2023-05-05 20:45:29 +02:00
legonzaur d55897964c fix image path 2023-05-05 20:45:11 +02:00
legonzaur 5850f086df Merge branch 'master' into server 2023-05-05 20:38:12 +02:00
legonzaur 2b130f052a add township 2023-05-05 20:38:08 +02:00
legonzaur 2d0d9e3a88 'publish' 2023-05-05 20:33:35 +02:00
legonzaur 488169889c Add favicon 2023-05-05 20:33:01 +02:00
legonzaur f6f3427e33 some enhancements to server list 2023-05-05 20:32:21 +02:00
legonzaur cd96631b9f fetch masterserver api every 60 secs 2023-05-05 20:32:07 +02:00
legonzaur 089fef92b1 add map images 2023-05-05 20:16:19 +02:00
legonzaur 1d2682111d 'publish' 2023-05-05 19:25:03 +02:00
legonzaur c64e85e039 dark theme 2023-05-05 19:24:32 +02:00
legonzaur 933f421313 working on server list 2023-05-05 19:23:46 +02:00
legonzaur c2227bffff handle realtime for servers 2023-05-05 17:45:05 +02:00
legonzaur 376fa73220 handle suicides for real time 2023-05-05 17:43:37 +02:00
legonzaur aa12b6fed6 apply route filters on view creation for context switching 2023-05-05 17:16:58 +02:00
legonzaur 87d06fa25c reggression for server select : grouping broke 2023-05-05 16:50:15 +02:00
legonzaur 45a8582aa4 add websockets 2023-05-05 16:23:04 +02:00
legonzaur b01958b1be 'publish' 2023-05-05 13:47:19 +02:00
legonzaur db59284b6f fix weapons not being sorted 2023-05-05 13:47:01 +02:00
legonzaur 61979d4aa9 'publish' 2023-05-05 12:39:29 +02:00
legonzaur 4d8421529e remove weapon without kills in weapon chart 2023-05-05 11:44:00 +02:00
legonzaur 1a16bb369b 'publish' 2023-05-04 21:54:11 +02:00
legonzaur 008c5a799a prevent scroll is no player is highlighted 2023-05-04 21:53:54 +02:00
legonzaur ef060980f7 'publish' 2023-05-04 21:36:06 +02:00
legonzaur dc772a2dab Some optimizations and virtual list 2023-05-04 21:35:33 +02:00
legonzaur 7755022227 Remove initial query to server 2023-05-04 18:20:26 +02:00
legonzaur 3e263211eb optimize data structure and refs 2023-05-04 11:48:40 +02:00
legonzaur 20b0e09cca 'publish' 2023-05-02 14:36:19 +02:00
legonzaur 78a4cc8a1b add social image 2023-05-02 14:35:54 +02:00
legonzaur 32efc16ae1 'publish' 2023-05-01 19:46:05 +02:00
legonzaur 33bd50081e disable clear button if filter can't be cleared 2023-05-01 19:45:50 +02:00
legonzaur 4824657f44 'publish' 2023-05-01 19:35:44 +02:00
legonzaur 50759f129c add query arguments for filters 2023-05-01 19:35:28 +02:00
legonzaur fd6ad61923 fix button margin for mobile UI 2023-05-01 17:54:30 +02:00
legonzaur 4e56f70fec 'publish' 2023-05-01 17:50:18 +02:00
legonzaur f815cb3d1a Add remove filter button 2023-05-01 17:50:01 +02:00
legonzaur ac57843c48 Fix responsive 2023-05-01 17:49:52 +02:00
legonzaur 93096daee3 'publish' 2023-05-01 17:28:15 +02:00
legonzaur c6ea1ce032 Split chart setup 2023-05-01 17:26:43 +02:00
legonzaur f65e4b20af Limit player dropdown to 20 entries 2023-05-01 17:26:17 +02:00
legonzaur d41b16e697 fix deselection of player 2023-05-01 15:51:40 +02:00
legonzaur 5f1611998a fix keyboard controls 2023-05-01 15:47:32 +02:00
legonzaur e155cb1013 'publish' 2023-05-01 15:23:51 +02:00
legonzaur b78072d385 speedup player List sorting 2023-05-01 15:23:25 +02:00
legonzaur 91977ded3d 'publish' 2023-04-28 17:24:30 +02:00
legonzaur b9fb1ce5a1 typo in CAR name 2023-04-28 17:24:07 +02:00
legonzaur 4b400348e6 'publish' 2023-04-28 17:21:50 +02:00
legonzaur bc1e23255d Add weapon translation 2023-04-28 17:21:26 +02:00
legonzaur dd075d2982 single API requests before recieving the data 2023-04-28 16:18:52 +02:00
legonzaur 0be6adb7d2 'publish' 2023-04-25 13:25:30 +02:00
legonzaur 0e0404ce0a fix production route 2023-04-25 13:25:08 +02:00
128 changed files with 51 additions and 14857 deletions
-4
View File
@@ -1,4 +0,0 @@
> 1%
last 2 versions
not dead
not ie 11
-5
View File
@@ -1,5 +0,0 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
-18
View File
@@ -1,18 +0,0 @@
module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/vue3-essential',
'@vue/standard',
'@vue/typescript/recommended'
],
parserOptions: {
ecmaVersion: 2020
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}
-23
View File
@@ -1,23 +0,0 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
-34
View File
@@ -1,34 +0,0 @@
# toneapi_webclient
Tone is an API that allows Northstar players to track their kill statistics : https://toneapi.github.io/ToneAPI_webclient/
If you want your server to appear on this website, you can download the server side mod from [here](https://github.com/ToneAPI/ToneAPI_servermod)
The server side mod will send kills data to Tone API, which will then process them.
You will have to set a personal token for the mod to work, please ask me for one on Discord
Alternatively, you can selfhost your own backend [here](https://github.com/ToneAPI/ToneAPI_backend)
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
-5
View File
@@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
+1
View File
@@ -0,0 +1 @@
.vlist[data-v-d1645486]{overflow-y:auto}.vlist[data-v-d1645486],.vscroll[data-v-d1645486]{height:calc(100% - 3em)}.inactive[data-v-12531736],.offline[data-v-12531736]{color:var(--comment)}.serverView[data-v-12531736]{margin:0 1rem 1rem 1rem;overflow:auto}.serverList[data-v-12531736]{overflow-y:auto;height:100%}.serverHeaders[data-v-12531736],.serverRow[data-v-12531736]{box-sizing:border-box;display:grid;grid-template-columns:4ch 178px 20ch 8ch 8ch 8ch 1fr;background:var(--bg-color);text-align:left;cursor:pointer;margin:0;transition:border .25s;height:100px}.serverHeaders[data-v-12531736]{height:3em;background:var(--current-line)}.serverHeaders>span[data-v-12531736]{padding:.5em .8em 0 .25em}.serverRow.odd[data-v-12531736]{background:var(--accent)}.serverRow.uneven[data-v-12531736]{background:var(--bg-color)}.serverRow[data-v-12531736]:hover{background:var(--current-line)}.serverRow>div[data-v-12531736]{padding:.5em .8em 0 .25em}.serverRow>div[data-v-12531736]:not(:last-child){border-right:solid var(--bg-color) 2px}img[data-v-12531736]{-o-object-fit:contain;object-fit:contain;height:100%}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
:root{--bg-color:#282a36;--accent:#353744;--current-line:#44475a;--comment:#6272a4;--foreground:#f8f8f2;--orange:#ffb86c;--cyan:#8be9fd;--green:#50fa7b;--pink:#ff79c6;--purple:#bd93f9;--red:#f55;--yellow:#f1fa8c}body{background:var(--bg-color);margin:0}#app{height:100vh;width:100vw;font-family:Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:var(--foreground);display:grid;grid-template-areas:"header header" "filters body " "stats body " "footer body ";grid-template-columns:1fr 5fr;grid-template-rows:auto 1fr auto auto}@media only screen and (max-width:922px){#app{grid-template-areas:"header header" "filters filters" "body body ";grid-template-rows:auto auto 1fr}}@media only screen and (max-width:600px){.github-link{display:none}}header{grid-area:header;height:64px;background:var(--bg-color);color:var(--foreground)}nav{display:flex;flex-direction:row;text-align:center;vertical-align:middle}nav a{font-weight:700;color:var(--foreground)}nav a.router-link-exact-active{color:var(--orange)}.navbar-logo img{height:48px;padding:8px 24px}.navbar-element{text-align:center;line-height:16px;padding:24px 32px}.navbar-element:hover,.navbar-logo:hover{background:var(--current-line)}.spacer{width:100%}
+1
View File
@@ -0,0 +1 @@
:root{--bg-color:#282a36;--accent:#353744;--current-line:#44475a;--comment:#6272a4;--foreground:#f8f8f2;--orange:#ffb86c;--cyan:#8be9fd;--green:#50fa7b;--pink:#ff79c6;--purple:#bd93f9;--red:#f55;--yellow:#f1fa8c}body{background:var(--bg-color);margin:0}#app{height:100vh;width:100vw;font-family:Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:var(--foreground);display:grid;grid-template-areas:"header header" "filters body " "stats body " "footer body ";grid-template-columns:1fr 5fr;grid-template-rows:auto 1fr auto auto}@media only screen and (max-width:922px){#app{grid-template-areas:"header header" "filters filters" "body body ";grid-template-rows:auto auto 1fr}}@media only screen and (max-width:600px){.github-link{display:none}}header{grid-area:header;height:64px;background:var(--bg-color);color:var(--foreground)}nav{display:flex;flex-direction:row;text-align:center;vertical-align:middle}nav a{font-weight:700;color:var(--foreground)}nav a.router-link-exact-active{color:var(--orange)}.navbar-logo img{height:48px;padding:8px 24px}.navbar-element{text-align:center;line-height:16px;padding:24px 32px}.navbar-element:hover,.navbar-logo:hover{background:var(--current-line)}.spacer{width:100%}
-1
View File
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

-1
View File
@@ -1 +0,0 @@
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/ToneAPI_webclient/favicon.ico"><title>toneapi_webclient</title><script defer="defer" src="/ToneAPI_webclient/js/chunk-vendors.3f4f208d.js"></script><script defer="defer" src="/ToneAPI_webclient/js/app.f8f821bc.js"></script><link href="/ToneAPI_webclient/css/app.bbc51a59.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but toneapi_webclient doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
-2
View File
File diff suppressed because one or more lines are too long
-1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

+1
View File
@@ -0,0 +1 @@
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><meta content="Players : Tone API" property="og:title"/><meta content="Northstar players kills statistics" property="og:description"/><meta content="https://toneapi.github.io/ToneAPI_webclient/" property="og:url"/><meta content="https://toneapi.github.io/ToneAPI_webclient/og_image.png" property="og:image"/><meta name="twitter:card" content="summary_large_image"><link rel="icon" href="/ToneAPI_webclient/favicon.ico"><title>Players : Tone API</title><script defer="defer" src="/ToneAPI_webclient/js/chunk-vendors.78b0bc71.js"></script><script defer="defer" src="/ToneAPI_webclient/js/index.ecca6261.js"></script><link href="/ToneAPI_webclient/css/index.85c03c07.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but Players : Tone API doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
+1
View File
@@ -0,0 +1 @@
"use strict";(self["webpackChunktoneapi_webclient"]=self["webpackChunktoneapi_webclient"]||[]).push([[855],{2677:function(e,t,s){s.d(t,{Z:function(){return v}});var i=s(3396),r=s(7139);const n={ref:"vscroll",class:"vscroll"};function a(e,t,s,a,l,o){return(0,i.wg)(),(0,i.iD)("div",{ref:"vlist",class:"vlist",onScroll:t[0]||(t[0]=(...t)=>e.scroll&&e.scroll(...t))},[(0,i._)("div",n,[(0,i._)("div",{style:(0,r.j5)(`padding: ${e.interval[0]*e.$props.rowHeight}px 0px ${Math.max(0,e.list.length*e.$props.rowHeight-e.interval[1]*e.$props.rowHeight)}px;`)},[((0,i.wg)(!0),(0,i.iD)(i.HY,null,(0,i.Ko)(e.visibleList,((t,s)=>(0,i.WI)(e.$slots,"default",{data:t,index:s+e.interval[0]},void 0,!0))),256))],4)],512)],544)}var l=(0,i.aZ)({data(){return{toLoad:10,vlistHeight:10,vIndex:0}},props:{list:{type:Array,default:()=>[]},rowHeight:{type:Number,default:()=>32},highlighted:String},mounted(){this.$props.list&&(this.vlistHeight=this.$refs.vlist?.getBoundingClientRect().height||0,this.toLoad=3*this.visibleCount,this.$props.highlighted&&setTimeout((()=>this.$refs.vlist?.scrollBy({top:this.list.findIndex((e=>e.id===this.$props.highlighted))*this.rowHeight-this.$refs.vlist?.scrollTop-(this.vlistHeight/2-this.$props.rowHeight),behavior:"smooth"})),1))},computed:{interval(){return[Math.max(Math.min(this.list.length-this.toLoad,this.vIndex),0),Math.min(this.vIndex+this.toLoad,this.list.length)]},visibleList(){return this.list.slice(this.interval[0],this.interval[1])},visibleCount(){return Math.ceil(this.vlistHeight/this.$props.rowHeight)}},watch:{list(){this.vlistHeight=this.$refs.vlist?.getBoundingClientRect().height||0},highlighted(e){e&&setTimeout((()=>this.$refs.vlist?.scrollBy({top:this.list.findIndex((t=>t.id===e))*this.rowHeight-this.$refs.vlist?.scrollTop-(this.vlistHeight/2-this.$props.rowHeight),behavior:"smooth"})),1)}},methods:{scroll(e){const t=e.target?.scrollTop,s=Math.floor(t/this.$props.rowHeight),i=s<this.vIndex,r=s>this.vIndex+this.visibleCount;if(i?this.vIndex=Math.max(s-this.visibleCount,0):r&&(this.vIndex=Math.min(s,this.list.length-this.toLoad)),r||i){}}}}),o=s(89);const h=(0,o.Z)(l,[["render",a],["__scopeId","data-v-d1645486"]]);var v=h},9855:function(e,t,s){s.r(t),s.d(t,{default:function(){return w}});var i=s(3396),r=s(7139);const n=e=>((0,i.dD)("data-v-12531736"),e=e(),(0,i.Cn)(),e),a=n((()=>(0,i._)("div",{id:"filters"},null,-1))),l={class:"serverView"},o={class:"serverList"},h=n((()=>(0,i._)("div",{class:"serverHeaders"},[(0,i._)("span"),(0,i._)("span",null,"Map"),(0,i._)("span",null,"Server name"),(0,i._)("span",null,"Players"),(0,i._)("span",null,"Kills"),(0,i._)("span",null,"Max distance"),(0,i._)("span",null,"Average distance")],-1))),v=["src"],d={key:1};function u(e,t,s,n,u,p){const c=(0,i.up)("VirtualList");return(0,i.wg)(),(0,i.iD)(i.HY,null,[a,(0,i._)("div",l,[(0,i._)("div",o,[h,(0,i.Wm)(c,{list:e.serverList,"row-height":100},{default:(0,i.w5)((t=>[(0,i._)("div",{class:(0,r.C_)("serverRow "+(e.nsServersByName[t.data.name]?"":"offline ")+(e.nsServersByName[t.data.name]?.playerCount?"":"inactive ")+((t.index+1)%2?"odd ":"uneven "))},[(0,i._)("div",null,[(0,i._)("span",null,(0,r.zw)(t.index+1),1)]),e.nsServersByName[t.data.name]?((0,i.wg)(),(0,i.iD)("img",{key:0,src:`${e.publicPath}maps/${e.nsServersByName[t.data.name].map}_lobby.png`},null,8,v)):(0,i.kq)("",!0),e.nsServersByName[t.data.name]?(0,i.kq)("",!0):((0,i.wg)(),(0,i.iD)("div",d)),(0,i._)("div",null,[(0,i._)("span",null,(0,r.zw)(t.data.name+(e.nsServersByName[t.data.name]?"":" (offline)")),1)]),(0,i._)("div",null,[(0,i._)("span",null,(0,r.zw)(e.nsServersByName[t.data.name]?e.nsServersByName[t.data.name]?.playerCount+"/"+e.nsServersByName[t.data.name]?.maxPlayers:""),1)]),(0,i._)("div",null,[(0,i._)("span",null,(0,r.zw)(t.data._value.deaths),1)]),(0,i._)("div",null,[(0,i._)("span",null,(0,r.zw)(t.data._value.max_distance),1)]),(0,i._)("div",null,[(0,i._)("span",null,(0,r.zw)(Math.round(100*(t.data._value.total_distance/t.data._value.kills||0))/100),1)])],2)])),_:1},8,["list"])])])],64)}var p=s(7672),c=s(2677),m=s(4870),g=(0,i.aZ)({name:"ServerView",components:{VirtualList:c.Z},data(){return{store:(0,p.D)(),publicPath:"/ToneAPI_webclient/"}},computed:{servers(){const e=this.store.getServerList({})?.value.data;return e||(0,m.SU)(this.store.fetchServers({})).data},serverList(){const e=Object.entries(this.servers).map((e=>({...e[1],name:e[0]})));return e.sort(((e,t)=>{const s=this.nsServersByName[e.name]?this.nsServersByName[e.name].playerCount?1:0:-1,i=this.nsServersByName[t.name]?this.nsServersByName[t.name].playerCount?1:0:-1;return i-s})),e},nsServers(){const e=this.store.getNSServers;return e||this.store.fetchNSServers()},nsServersByName(){return Object.fromEntries(this.nsServers.map((e=>[e.name,e])))}}}),_=s(89);const f=(0,_.Z)(g,[["render",u],["__scopeId","data-v-12531736"]]);var w=f}}]);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

-13463
View File
File diff suppressed because it is too large Load Diff
-43
View File
@@ -1,43 +0,0 @@
{
"name": "toneapi_webclient",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"assert": "^2.0.0",
"chart.js": "^4.2.1",
"chartjs-plugin-annotation": "^2.2.1",
"chartjs-plugin-datalabels": "^2.2.0",
"core-js": "^3.8.3",
"pinia": "^2.0.35",
"vue": "^3.2.13",
"vue-chartjs": "^5.2.0",
"vue-class-component": "^8.0.0-0",
"vue-multiselect": "^3.0.0-beta.1",
"vue-router": "^4.0.3"
},
"devDependencies": {
"@types/assert": "^1.5.6",
"@types/node": "^18.16.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-typescript": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/eslint-config-standard": "^6.1.0",
"@vue/eslint-config-typescript": "^9.1.0",
"eslint": "^7.32.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^8.0.3",
"typescript": "~4.5.5"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

-17
View File
@@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
-4
View File
@@ -1,4 +0,0 @@
call npm run build
call git add dist -f
call git commit -m 'publish'
call git subtree push --prefix dist origin gh-pages
Binary file not shown.

After

Width:  |  Height:  |  Size: 512 KiB

+1
View File
@@ -0,0 +1 @@
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><meta content="Servers : Tone API" property="og:title"/><meta content="Northstar servers kills statistics" property="og:description"/><meta content="https://toneapi.github.io/ToneAPI_webclient/servers" property="og:url"/><meta content="https://toneapi.github.io/ToneAPI_webclient/server_og_image.png?" property="og:image"/><meta name="twitter:card" content="summary_large_image"><link rel="icon" href="/ToneAPI_webclient/favicon.ico"><title>Servers : Tone API</title><script defer="defer" src="/ToneAPI_webclient/js/chunk-vendors.78b0bc71.js"></script><script defer="defer" src="/ToneAPI_webclient/js/server.0510ec8b.js"></script><link href="/ToneAPI_webclient/css/server.85c03c07.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but Servers : Tone API doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
-123
View File
@@ -1,123 +0,0 @@
<template>
<header>
<nav>
<!-- <div class="navbar-element">hamburger</div> -->
<div class="navbar-element"><!--<span>logo&nbsp;</span>--->TONE&nbsp;API</div>
<div class="navbar-element github-link"><a href="https://github.com/Legonzaur/Tone_WebClient">Github</a></div>
<div class="navbar-element github-link"><a
href="https://github.com/Legonzaur/ToneAPI_servermod">Northstar&nbsp;Server&nbsp;Mod</a></div>
<div class="spacer"></div>
<!-- <router-link to="/" class="navbar-element">Home</router-link>
<router-link to="/about" class="navbar-element">About</router-link> -->
<router-link to="/" class="navbar-element">Players</router-link>
</nav>
</header>
<router-view />
</template>
<script lang="ts">
import { Player, Weapon, Server, useKillStore } from '@/stores/kill'
import { defineComponent } from 'vue'
export default defineComponent({
setup (props, ctx) {
const store = useKillStore()
store.fetchPlayers({})
store.fetchServers({})
store.fetchWeapons({})
}
})
</script>
<style>
:root {
--bg-color: #282a36;
--accent: #353744;
--current-line: #44475a;
--comment: #6272a4;
--foreground: #f8f8f2;
--orange: #ffb86c;
--cyan: #8be9fd;
--green: #50fa7b;
--pink: #ff79c6;
--purple: #bd93f9;
--red: #ff5555;
--yellow: #f1fa8c;
}
body {
background: var(--bg-color);
margin: 0;
}
#app {
height: 100vh;
width: 100vw;
font-family: 'Helvetica', 'Arial', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: var(--foreground);
display: grid;
grid-template-areas:
'header header'
'filters body '
'stats body '
'footer body ';
grid-template-columns: 1fr 5fr;
grid-template-rows: auto 1fr auto auto;
}
@media only screen and (max-width: 922px) {
#app {
grid-template-areas:
'header header'
'filters filters'
'body body ';
grid-template-rows: auto auto 1fr;
}
}
@media only screen and (max-width: 600px) {
.github-link{
display:none;
}
}
header {
grid-area: header;
height: 64px;
background: var(--bg-color);
color: var(--foreground);
}
nav {
display: flex;
flex-direction: row;
text-align: center;
vertical-align: middle;
}
nav a {
font-weight: bold;
color: var(--foreground);
}
nav a.router-link-exact-active {
color: var(--orange);
}
.navbar-element {
text-align: center;
line-height: 16px;
padding: 24px 32px;
}
.navbar-element:hover {
background: var(--current-line)
}
.spacer {
width: 100%;
}
</style>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

-65
View File
@@ -1,65 +0,0 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">typescript</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component'
@Options({
props: {
msg: String
}
})
export default class HelloWorld extends Vue {
msg!: string
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
-211
View File
@@ -1,211 +0,0 @@
<template>
<div class="playerChart" ref="container">
<!-- <Scatter :data="chart" :options="chartOptions" /> -->
<Scatter :data="chart" :options="chartOptions" />
</div>
</template>
<script lang="ts">
import {
Chart as ChartJS,
LinearScale,
PointElement,
LineElement,
Tooltip,
Legend,
CoreChartOptions,
PluginChartOptions,
ScaleChartOptions
} from 'chart.js'
import annotationPlugin from 'chartjs-plugin-annotation'
import dataLabel from 'chartjs-plugin-datalabels'
import { useKillStore, Player, Filters } from '@/stores/kill'
import { Scatter } from 'vue-chartjs'
import { defineComponent, PropType } from 'vue'
import { ChartEvent } from 'chart.js/dist/core/core.plugins'
import { _DeepPartialObject } from 'chart.js/dist/types/utils'
ChartJS.register(LinearScale, PointElement, LineElement, Tooltip, Legend, annotationPlugin, dataLabel)
export default defineComponent({
name: 'PlayerChart',
props: {
filters: Object as PropType<Filters>,
playerHighlighted: String
},
data: () => {
return {
store: useKillStore(),
refreshColors: 0
}
},
emits: ['highlightPlayer'],
components: {
Scatter
},
mounted () {
this.refreshColors++
},
computed: {
colors () {
// eslint-disable-next-line no-unused-expressions
this.refreshColors
const colors = {} as { [key: string]: string }
if (this.$refs.container) {
const style = getComputedStyle(this.$refs.container as Element)
colors.fg = style.getPropertyValue('--foreground')
colors.bg = style.getPropertyValue('--bg-color')
colors.orange = style.getPropertyValue('--orange')
colors.cyan = style.getPropertyValue('--cyan')
colors.currentLine = style.getPropertyValue('--current-line')
} else {
colors.fg = '#ffffff'
}
return colors
},
chart () {
return {
datasets: [
{
label: 'Players',
labels: this.playerIdList || [] as string[],
borderColor: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.index]) ? this.colors.orange : this.colors.cyan,
backgroundColor: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.index]) ? this.colors.orange : this.colors.cyan,
pointRadius: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.index]) ? 20 : 1,
pointStyle: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.index]) ? 'crossRot' : 'circle',
hoverRadius: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.index]) ? 30 : 4,
data: this.playerIdList.map((id) => ({ y: this.players[id].kills, x: this.players[id].deaths }))
}
]
}
},
chartOptions (): _DeepPartialObject<CoreChartOptions<'scatter'> & PluginChartOptions<'scatter'> & ScaleChartOptions<'scatter'>> {
// eslint-disable-next-line no-unused-expressions
this.$props.playerHighlighted
let endX = 0
let endY = 0
if (this.players) {
const maxX = Math.max(...Object.values(this.players).map(e => e.deaths))
const maxY = Math.max(...Object.values(this.players).map(e => e.kills))
if (maxY > maxX) {
endX = maxX
endY = (maxX / maxY) * maxY
} else {
endY = maxY
endX = (maxY / maxX) * maxX
}
}
return {
responsive: true,
maintainAspectRatio: false,
animation: { duration: 500 },
layout: { autoPadding: false },
scales: {
y: {
title: {
text: 'Kills',
display: true,
color: this.colors.fg
},
ticks: {
color: this.colors.fg
}
},
x: {
title: {
text: 'Deaths',
display: true,
color: this.colors.fg
},
ticks: {
color: this.colors.fg
}
}
},
plugins: {
tooltip: {
callbacks: {
label: (ctx: any) => {
let label: string
if (!this.players) {
label = ctx.dataset.labels[ctx.dataIndex]
} else {
label = this.players[ctx.dataset.labels[ctx.dataIndex]].username
}
label += ' (' + ctx.parsed.y + ' kills ' + ctx.parsed.x + ' deaths)'
return label
}
}
},
annotation: {
annotations: {
line: {
type: 'line',
yMin: 0,
xMin: 0,
yMax: endY,
xMax: endX,
borderWidth: 2,
borderColor: this.colors.orange,
borderDash: [1, 5]
}
}
},
legend: { display: false },
datalabels: {
formatter: (value: any, context: any) => {
return this.players[this.playerIdList[context.dataIndex]].username
},
backgroundColor: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.dataIndex]) ? this.colors.orange : null,
borderColor: this.colors.fg,
borderWidth: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.dataIndex]) ? 1 : 0,
borderRadius: 5,
display: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.dataIndex]) ? 'true' : 'auto',
align: -45,
anchor: 'end',
clamp: true,
color: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.dataIndex]) ? this.colors.bg : this.colors.fg
}
},
onClick: (e: ChartEvent, element: any) => {
this.$emit('highlightPlayer', element.length > 0 ? this.playerIdList[element[0].index] : undefined)
},
onHover: (e: any, element: any) => {
if (!e.native.target) return
(e.native.target as HTMLElement).style.cursor = element[0] ? 'pointer' : 'default'
}
}
},
players (): { [key: string]: Player } {
const data = this.store.getPlayerList(this.filters || {})?.data
if (!data) return {}
const cut = Object.entries(data).sort((a, b) => {
if (a[1].kills < b[1].kills) {
return 1
}
if (a[1].kills > b[1].kills) {
return -1
}
return 0
}).slice(0, 200)
return Object.fromEntries(cut)
},
playerIdList (): string[] { return Object.keys(this.players) }
}
})
</script>
<style scoped>
canvas {
background: var(--accent);
}
@media only screen and (max-width: 922px) {
canvas {
display: none !important
}
}
</style>
-221
View File
@@ -1,221 +0,0 @@
<template>
<div class="playerTable" v-on:keydown="selectNextPlayer" tabindex="0">
<div class="playerHeaders">
<span></span>
<span v-on:click="sortPlayerList('username')"
:class="sortingData.argument == 'username' ? 'selected' : ''">Username</span>
<span v-on:click="sortPlayerList('kills')" :class="sortingData.argument == 'kills' ? 'selected' : ''">K</span>
<span v-on:click="sortPlayerList('deaths')" :class="sortingData.argument == 'deaths' ? 'selected' : ''">D</span>
<span v-on:click="sortPlayerList('k/d')" :class="sortingData.argument == 'k/d' ? 'selected' : ''">K/D</span>
<span v-on:click="sortPlayerList('max_distance')"
:class="sortingData.argument == 'max_distance' ? 'selected' : ''">max distance</span>
<span v-on:click="sortPlayerList('avg_distance')"
:class="sortingData.argument == 'avg_distance' ? 'selected' : ''">average distance</span>
</div>
<div :class="'playerRow ' + (playerId === $props.playerHighlighted ? 'selected' : '')"
v-for="(playerId, index) in playerIdList" v-bind:key="playerId" v-on:click="$emit('highlightPlayer', playerId)"
:ref="`player:` + playerId">
<div><span>{{ index+1 }}</span></div>
<div><span>{{ players[playerId].username }}</span></div>
<div><span>{{ players[playerId].kills }}</span></div>
<div><span>{{ players[playerId].deaths }}</span></div>
<div><span>{{ Math.round(players[playerId].kills / Math.max(1, players[playerId].deaths) * 100) / 100 }}</span></div>
<div><span>{{ players[playerId].max_distance }}</span></div>
<div><span>{{ Math.round(((players[playerId].total_distance / players[playerId].kills) || 0) * 100) / 100 }}</span></div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
import { useKillStore, Player, Filters } from '@/stores/kill'
export default defineComponent({
props: {
filters: { type: Object as PropType<Filters>, default: () => ({}) },
playerHighlighted: String
},
emits: ['highlightPlayer'],
data () {
return {
sortingData: { direction: -1, argument: 'kills' as keyof Player | 'k/d' | 'avg_distance' },
playerIdList: [] as string[],
store: useKillStore()
}
},
computed: {
players (): { [key: string]: Player } {
const { player: _, ...withoutPlayer } = this.filters
return this.store.getPlayerList(withoutPlayer)?.data || {}
}
},
watch: {
players: {
handler (newval: { [key: string]: Player }): void {
if (!newval) return
this.playerIdList = []
this.sortingData.direction *= -1
this.playerIdList = Object.keys(newval)
this.sortPlayerList(this.sortingData.argument)
this.playerIdList = this.playerIdList.slice(0, 500)
},
immediate: true
},
playerHighlighted (newval) {
const newElement = this.$refs['player:' + newval] as HTMLElement[]
if (newElement && newElement[0]) { newElement[0].scrollIntoView({ behavior: 'smooth', block: 'center' }) }
}
},
updated () {
const element = this.$refs['player:' + this.$props.playerHighlighted] as HTMLElement[]
if (!element || !element[0]) return
element[0].scrollIntoView({ behavior: 'smooth', block: 'center' })
},
methods: {
sortPlayerList (arg: ((keyof Player) | 'k/d' | 'avg_distance')) {
if (arg === this.sortingData.argument) {
this.sortingData.direction *= -1
} else {
if (arg === 'username') {
this.sortingData.direction = 1
}
}
this.sortingData.argument = arg
this.playerIdList.sort((a: string, b: string) => {
if (!this.players) {
return 0
}
let varA, varB
if (arg === 'k/d') {
varA = this.players[a].kills / Math.max(1, this.players[a].deaths)
varB = this.players[b].kills / Math.max(1, this.players[b].deaths)
} else if (arg === 'avg_distance') {
varA = (this.players[a].total_distance / this.players[a].kills) || 0
varB = (this.players[b].total_distance / this.players[b].kills) || 0
} else {
varA = this.players[a][arg]
varB = this.players[b][arg]
}
if (varA === undefined || varB === undefined) return 0
if (varA < varB) {
return -1 * this.sortingData.direction
}
if (varA > varB) {
return 1 * this.sortingData.direction
}
return 0
})
},
selectNextPlayer (e: KeyboardEvent) {
const values = {
Home: -Infinity,
End: Infinity,
PageDown: 100,
PageUp: -100,
ArrowUp: -1,
ArrowDown: 1,
ArrowRight: 10,
ArrowLeft: -10
} as { [key: string]: number }
if (!this.$props.playerHighlighted) return
if (!Object.keys(values).includes(e.key)) return
e.preventDefault()
const index = this.playerIdList.indexOf(this.$props.playerHighlighted)
let nextIndex = (index + values[e.key])
if (nextIndex >= this.playerIdList.length) nextIndex = this.playerIdList.length - 1
if (nextIndex < 0) nextIndex = 0
this.$emit('highlightPlayer', this.playerIdList[nextIndex])
}
}
})
</script>
<style scoped>
* {
box-sizing: border-box;
}
.playerTable {
grid-area: list;
margin-right: 1rem;
overflow: auto;
height: 100%;
user-select: none;
}
.playerRow,
.playerHeaders {
box-sizing: border-box;
display: grid;
grid-template-columns: 5ch 20ch 6ch 6ch 6ch 10ch 1fr;
background: var(--bg-color);
text-align: left;
cursor: pointer;
margin: 0;
/* border: 2px solid transparent; */
transition: border .25s;
height: 2em;
}
.playerHeaders {
position: sticky;
top: 0;
height: 3em;
}
.playerHeaders>span {
padding: .5em .8em 0 .25em;
}
.playerRow>div {
padding: .5em .8em 0 .25em;
overflow: hidden;
}
.playerHeaders,
.playerRow:nth-child(2n) {
background: var(--current-line);
}
.playerRow:nth-child(2n+1) {
background: var(--accent);
}
.playerHeaders div:hover,
.playerRow:hover {
background: var(--bg-color);
}
.playerRow>div:not(:last-child),
.playerHeaders>div:not(:last-child) {
border-right: solid var(--bg-color) 2px;
}
.playerHeaders>span:last-child {
width: 100%;
}
@media only screen and (max-width: 600px) {
.playerRow>div:nth-child(6),
.playerHeaders>span:nth-child(6),
.playerRow>div:nth-child(7),
.playerHeaders>span:nth-child(7) {
display: none;
}
.playerRow,
.playerHeaders {
grid-template-columns: 5ch 20ch 6ch 6ch 1fr;
}
}
.player span {
pointer-events: none;
}
.selected {
background: var(--comment) !important;
}</style>
-176
View File
@@ -1,176 +0,0 @@
<template>
<div class="weaponChart" ref="container">
<Doughnut :data="chart" :options="chartOptions" />
</div>
</template>
<script lang="ts">
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'
import dataLabel from 'chartjs-plugin-datalabels'
import { Weapon, Filters, useKillStore } from '@/stores/kill'
import { Doughnut } from 'vue-chartjs'
import { defineComponent, PropType } from 'vue'
ChartJS.register(ArcElement, Tooltip, Legend, dataLabel)
export default defineComponent({
name: 'PlayerChart',
props: {
filters: Object as PropType<Filters>,
playerHighlighted: String
},
emits: ['highlightPlayer'],
components: {
Doughnut
},
mounted () {
this.refreshColors++
},
data: () => {
return {
store: useKillStore(),
refreshColors: 0
}
},
computed: {
colors () {
// eslint-disable-next-line no-unused-expressions
this.refreshColors
const colors = {} as { [key: string]: string }
if (this.$refs.container) {
const style = getComputedStyle(this.$refs.container as Element)
colors.fg = style.getPropertyValue('--foreground')
colors.bg = style.getPropertyValue('--bg-color')
colors.orange = style.getPropertyValue('--orange')
colors.cyan = style.getPropertyValue('--cyan')
colors.yellow = style.getPropertyValue('--yellow')
colors.green = style.getPropertyValue('--green')
colors.purple = style.getPropertyValue('--purple')
colors.red = style.getPropertyValue('--red')
colors.pink = style.getPropertyValue('--pink')
colors.currentLine = style.getPropertyValue('--current-line')
} else {
colors.fg = '#ffffff'
}
return colors
},
chart () {
const colors = [
'cyan',
'green',
'orange',
'pink',
'purple',
'red',
'yellow'
]
const chartData = {
datasets: [
{
label: 'Weapons',
labels: this.sortedWeaponList || ([] as string[]),
borderColor: () => this.colors.fg,
backgroundColor: (context: any) => {
return this.colors[
colors[(context.dataIndex * 3) % colors.length]
]
},
data: [] as number[]
}
]
}
if (!this.sortedWeaponList) {
return chartData
}
chartData.datasets[0].data = this.sortedWeaponList.map((e) => {
if (!this.weapons) {
return 0
}
return this.weapons[e].kills
})
return chartData
},
chartOptions () {
// eslint-disable-next-line no-unused-expressions
this.$props.playerHighlighted
const options = {
responsive: true,
maintainAspectRatio: true,
animation: { duration: 500 },
layout: { autoPadding: false },
plugins: {
datalabels: {
formatter: (value: any, context: any) => {
return this.sortedWeaponList[context.dataIndex]
},
backgroundColor: (context: any) => {
return context.dataset.backgroundColor(context)
},
borderColor: this.colors.fg,
borderRadius: 25,
borderWidth: 2,
color: this.colors.bg,
display: (context: any) => {
return context.dataIndex === context.dataset.labels.length - 1
? true
: 'auto'
},
padding: 6
},
tooltip: {
callbacks: {
label: (ctx: any) => {
let label: string
if (!this.sortedWeaponList) {
label = ctx.dataset.labels[ctx.dataIndex]
} else {
label = this.sortedWeaponList[ctx.dataIndex]
}
label += ' (' + this.weapons[label].kills + ' kills)'
return label
}
}
}
}
}
return options
},
weapons (): { [key: string]: Weapon } {
const { weapon: _, ...withoutWeapon } = this.filters || {}
const data = this.store.getWeaponList(withoutWeapon)?.data
if (!data) {
this.store.fetchWeapons(withoutWeapon)
return {}
}
return data
},
sortedWeaponList (): string[] {
if (!this.weapons) return []
const weapons = Object.keys(this.weapons)
weapons.sort((a, b) => {
if (Number(this.weapons[a].kills) < Number(this.weapons[b].kills)) {
return -1
}
if (Number(this.weapons[a].kills) > Number(this.weapons[b].kills)) {
return 1
}
return 0
})
return weapons
}
}
})
</script>
<style scoped>
.weaponChart {
width: calc(min(50vw, 50vh) - 3em);
height: calc(min(50vw, 50vh) - 3em);
}
@media only screen and (max-width: 922px) {
canvas {
display: none !important;
}
}
</style>
-8
View File
@@ -1,8 +0,0 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'
const pinia = createPinia()
createApp(App).use(pinia).use(router).mount('#app')
-31
View File
@@ -1,31 +0,0 @@
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import PlayerView from '../views/PlayerView.vue'
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'home',
component: PlayerView
}
// {
// path: '/players',
// name: 'players',
// component: PlayerView
// },
// {
// path: '/about',
// name: 'about',
// // route level code-splitting
// // this generates a separate chunk (about.[hash].js) for this route
// // which is lazy-loaded when the route is visited.
// component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
// }
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
-6
View File
@@ -1,6 +0,0 @@
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
-113
View File
@@ -1,113 +0,0 @@
import { defineStore } from 'pinia'
import * as assert from 'assert'
function removeNullEntries (a:any) {
return Object.fromEntries(Object.entries(a).filter(e => e[1] !== undefined && e[1] !== null))
}
function objectEqual (a:any, b:any) {
try {
a = removeNullEntries(a)
b = removeNullEntries(b)
assert.deepStrictEqual(a, b)
return true
} catch {
return false
}
}
export interface Filters {
server?: string;
player?: string;
weapon?: string;
host?: string;
map?: string;
gamemode?: string;
}
export interface Kill {
deaths: number;
kills: number;
// eslint-disable-next-line camelcase
max_distance: number;
// eslint-disable-next-line camelcase
total_distance: number;
}
export interface KillData<T extends Kill> {
filter: Filters;
data: { [key: string]: T };
}
export interface Player extends Kill {
username: string;
// eslint-disable-next-line camelcase
deaths_while_equipped?: number;
}
export interface Weapon extends Kill {
// eslint-disable-next-line camelcase
deaths_while_equipped: number;
}
export interface Server extends Kill {
host: number;
}
// define your typings for the store state
export interface State {
servers: KillData<Server>[];
players: KillData<Player>[];
weapons: KillData<Weapon>[];
maps: KillData<Kill>[];
gamemodes: KillData<Kill>[];
hosts: { [key: number]: string };
}
export const useKillStore = defineStore('kill', {
state: (): State => ({
servers: [],
players: [],
weapons: [],
maps: [],
gamemodes: [],
hosts: {}
}),
getters: {
getPlayerList: (state) => (filters: Filters) => state.players.find((e) => objectEqual(e.filter, filters)),
getWeaponList: (state) => (filters: Filters) => state.weapons.find((e) => objectEqual(e.filter, filters)),
getServerList: (state) => (filters: Filters) => state.servers.find((e) => objectEqual(e.filter, filters))
},
actions: {
async fetchPlayers (filter: Filters) {
filter = removeNullEntries(filter)
const response = await fetch(
'https://tone.sleepycat.date/v2_test/client/players?' +
new URLSearchParams(filter as Record<keyof Filters, string>)
)
const data = await response.json()
const entry = this.players.find((e) => objectEqual(e.filter, filter))
if (!entry) return this.players.push({ filter, data })
entry.data = data
},
async fetchWeapons (filter: Filters) {
filter = removeNullEntries(filter)
const response = await fetch(
'https://tone.sleepycat.date/v2_test/client/weapons?' +
new URLSearchParams(filter as Record<keyof Filters, string>)
)
const data = await response.json()
const entry = this.weapons.find((e) => objectEqual(e.filter, filter))
if (!entry) return this.weapons.push({ filter, data })
entry.data = data
},
async fetchServers (filter: Filters) {
filter = removeNullEntries(filter)
const response = await fetch(
'https://tone.sleepycat.date/v2_test/client/servers?' +
new URLSearchParams(filter as Record<keyof Filters, string>)
)
const data = await response.json()
const entry = this.servers.find((e) => objectEqual(e.filter, filter))
if (!entry) return this.servers.push({ filter, data })
entry.data = data
}
}
})
-5
View File
@@ -1,5 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
-18
View File
@@ -1,18 +0,0 @@
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
</div>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component'
import HelloWorld from '@/components/HelloWorld.vue' // @ is an alias to /src
@Options({
components: {
HelloWorld
}
})
export default class HomeView extends Vue {}
</script>
-173
View File
@@ -1,173 +0,0 @@
<template>
<div id="filters">
<!-- <input type="number" min="0" v-model="filters.minKills">
<input type="number" min="0" v-model="filters.minDeaths"> -->
<!-- <select v-on:change="changeFilter({ server: ($event.target as HTMLInputElement).value })">
<option></option>
<option v-for="(serverData) in servers" v-bind:key="serverData.id" :value="serverData.id">{{ serverData.name }}
</option>
</select>
<select v-on:change="changeFilter({ weapon: ($event.target as HTMLInputElement).value })">
<option></option>
<option v-for="weaponId in sortedWeaponList" v-bind:key="weaponId" :value="weaponId">{{ weaponId }}</option>
</select> -->
<VueMultiselect selectLabel="" deselectLabel="remove" placeholder="Select server" v-model="model.server"
:options="groupedServers" group-values="servers" group_label="id" :group-select="false" :allow-empty="true" :multiple="false" :custom-label="((e:any) => e.name)" track-by="name" label="name"
></VueMultiselect>
<VueMultiselect selectLabel="" deselectLabel="remove" placeholder="Select weapon" v-model="model.weapon"
:options="sortedWeaponList" :allow-empty="true"
></VueMultiselect>
<VueMultiselect selectLabel="" deselectLabel="remove" placeholder="Search player" v-model="playerHighlighted"
:options="sortedPlayerList" :allow-empty="true" :custom-label="((e:string) => players[e]?.username)"></VueMultiselect>
</div>
<div id="playerView">
<PlayerList :filters="filters" v-on:highlight-player="playerHighlighted = $event"
:playerHighlighted="playerHighlighted"></PlayerList>
<PlayerChart :filters="filters" v-on:highlight-player="playerHighlighted = $event"
:playerHighlighted="playerHighlighted"></PlayerChart>
<WeaponChart :filters="{player: playerHighlighted, ...filters}" :playerHighlighted="playerHighlighted"></WeaponChart>
</div>
</template>
<script lang="ts">
import { Player, Weapon, Server, useKillStore, Filters } from '@/stores/kill'
import PlayerList from '@/components/PlayerList.vue'
import PlayerChart from '@/components/PlayerChart.vue'
import WeaponChart from '@/components/WeaponChart.vue'
import { defineComponent } from 'vue'
import VueMultiselect from 'vue-multiselect'
import 'vue-multiselect/dist/vue-multiselect.css'
export default defineComponent({
name: 'PlayerView',
components: {
VueMultiselect, PlayerList, PlayerChart, WeaponChart
},
data () {
return {
model: { server: undefined, weapon: undefined } as unknown as {weapon:Weapon | Weapon[], server: (Server& {name?:string}) | (Server& {name?:string})[]},
filters: {} as Filters,
store: useKillStore(),
playerHighlighted: undefined
}
},
computed: {
servers (): { [key: string]: Server } {
const { server: _, player: _1, ...withoutServer } = this.filters
const data = this.store.getServerList(withoutServer)?.data
if (!data) {
this.store.fetchServers(withoutServer)
return {}
}
return data
},
groupedServers () {
const hosts = [] as {id:string, servers:(Server& {name?:string})[] }[]
Object.entries(this.servers).forEach((e) => {
if (!hosts.find(host => host.id === e[1].host + 'sfdsdfsd')) hosts.push({ id: e[1].host + 'sfdsdfsd', servers: [] as Server[] })
hosts.find(host => host.id === e[1].host + 'sfdsdfsd')?.servers.push({ name: e[0], ...e[1] })
})
return hosts
},
weapons (): { [key: string]: Weapon } {
const { weapon: _, player: _1, ...withoutWeapons } = this.filters
const data = this.store.getWeaponList(withoutWeapons)?.data
if (!data) {
this.store.fetchWeapons(withoutWeapons)
return {}
}
return data
},
players (): { [key: string]: Player } {
const { player: _, ...withoutPlayers } = this.filters
const data = this.store.getPlayerList(withoutPlayers)?.data
if (!data) {
this.store.fetchPlayers(withoutPlayers)
return {}
}
return data
},
sortedWeaponList (): string[] {
if (!this.weapons) return []
const weapons = Object.keys(this.weapons)
return weapons.sort()
},
sortedServerList ():string[] {
if (!this.servers) return []
const servers = Object.keys(this.servers)
return servers.sort()
},
sortedPlayerList (): string[] {
if (!this.players) return []
const players = Object.keys(this.players)
return players.sort((a: string, b: string) => {
if (!this.players) {
return 0
}
if (this.players[a].username < this.players[b].username) {
return -1
}
if (this.players[a].username > this.players[b].username) {
return 1
}
return 0
})
}
},
watch: {
model: {
handler (newModel) {
this.filters.server = newModel.server?.name
this.filters.weapon = newModel.weapon
},
deep: true
}
}
})
</script>
<style scoped>
.multiselect {
margin: 0 .5em 1em .5em;
}
#playerView {
margin: 0 1rem 1rem 1rem;
/* height:100%; */
display: grid;
overflow: auto;
grid-template-areas:
'list chart'
'list info';
grid-template-columns: 50% 50%;
grid-template-rows: 50% 50%;
}
@media only screen and (max-width: 922px) {
#playerView {
margin: 0 1rem 1rem 1rem;
/* height:100%; */
display: grid;
overflow: auto;
grid-template-areas:
'list list';
grid-template-columns: 100%;
grid-template-rows: 100%;
width: calc(100vw - 1em);
margin: 0 0rem 1rem 1rem;
}
.multiselect {
width: calc(100% - 1em)
}
}
#filters {
grid-area: filters;
}
</style>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

-38
View File
@@ -1,38 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"moduleResolution": "node",
"experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
]
}
-5
View File
@@ -1,5 +0,0 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
publicPath: process.env.NODE_ENV === 'production' ? '/ToneAPI_webclient/' : '/'
})
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Some files were not shown because too many files have changed in this diff Show More