mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-05 22:36:05 +02:00
feat: Instance Activity Chart (#1141)
* feat: Instance Activity Chart * fix: chart data handling
This commit is contained in:
+3
-2
@@ -1,13 +1,14 @@
|
|||||||
{
|
{
|
||||||
"root": true,
|
"root": true,
|
||||||
"extends": ["eslint:all", "prettier"],
|
"extends": ["eslint:all", "plugin:vue/recommended", "prettier"],
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"commonjs": true,
|
"commonjs": true,
|
||||||
"es2021": true
|
"es2021": true
|
||||||
},
|
},
|
||||||
"parser": "@babel/eslint-parser",
|
"parser": "vue-eslint-parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
|
"parser": "@babel/eslint-parser",
|
||||||
"ecmaVersion": "latest",
|
"ecmaVersion": "latest",
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
|
|||||||
Generated
+251
-8
@@ -25,12 +25,15 @@
|
|||||||
"copy-webpack-plugin": "^12.0.2",
|
"copy-webpack-plugin": "^12.0.2",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "^7.1.2",
|
"css-loader": "^7.1.2",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"default-passive-events": "^2.0.0",
|
"default-passive-events": "^2.0.0",
|
||||||
|
"echarts": "^5.6.0",
|
||||||
"electron": "^34.0.2",
|
"electron": "^34.0.2",
|
||||||
"electron-builder": "^25.1.8",
|
"electron-builder": "^25.1.8",
|
||||||
"element-ui": "^2.15.14",
|
"element-ui": "^2.15.14",
|
||||||
"eslint": "^9.19.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
|
"eslint-plugin-vue": "^9.32.0",
|
||||||
"html-webpack-plugin": "^5.6.3",
|
"html-webpack-plugin": "^5.6.3",
|
||||||
"mini-css-extract-plugin": "^2.9.2",
|
"mini-css-extract-plugin": "^2.9.2",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
@@ -1967,9 +1970,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "9.19.0",
|
"version": "9.20.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.20.0.tgz",
|
||||||
"integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==",
|
"integrity": "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -4811,6 +4814,13 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dayjs": {
|
||||||
|
"version": "1.11.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
||||||
|
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/de-indent": {
|
"node_modules/de-indent": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
|
||||||
@@ -5164,6 +5174,24 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/echarts": {
|
||||||
|
"version": "5.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
|
||||||
|
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "2.3.0",
|
||||||
|
"zrender": "5.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/echarts/node_modules/tslib": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/ejs": {
|
"node_modules/ejs": {
|
||||||
"version": "3.1.10",
|
"version": "3.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||||
@@ -5439,18 +5467,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "9.19.0",
|
"version": "9.20.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz",
|
||||||
"integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==",
|
"integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
"@eslint/config-array": "^0.19.0",
|
"@eslint/config-array": "^0.19.0",
|
||||||
"@eslint/core": "^0.10.0",
|
"@eslint/core": "^0.11.0",
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
"@eslint/js": "9.19.0",
|
"@eslint/js": "9.20.0",
|
||||||
"@eslint/plugin-kit": "^0.2.5",
|
"@eslint/plugin-kit": "^0.2.5",
|
||||||
"@humanfs/node": "^0.16.6",
|
"@humanfs/node": "^0.16.6",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
@@ -5511,6 +5539,85 @@
|
|||||||
"eslint": ">=7.0.0"
|
"eslint": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint-plugin-vue": {
|
||||||
|
"version": "9.32.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.32.0.tgz",
|
||||||
|
"integrity": "sha512-b/Y05HYmnB/32wqVcjxjHZzNpwxj1onBOvqW89W+V+XNG1dRuaFbNd3vT9CLbr2LXjEoq+3vn8DanWf7XU22Ug==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
|
"globals": "^13.24.0",
|
||||||
|
"natural-compare": "^1.4.0",
|
||||||
|
"nth-check": "^2.1.1",
|
||||||
|
"postcss-selector-parser": "^6.0.15",
|
||||||
|
"semver": "^7.6.3",
|
||||||
|
"vue-eslint-parser": "^9.4.3",
|
||||||
|
"xml-name-validator": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.17.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint-plugin-vue/node_modules/globals": {
|
||||||
|
"version": "13.24.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
|
||||||
|
"integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"type-fest": "^0.20.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint-plugin-vue/node_modules/postcss-selector-parser": {
|
||||||
|
"version": "6.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
||||||
|
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cssesc": "^3.0.0",
|
||||||
|
"util-deprecate": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint-plugin-vue/node_modules/semver": {
|
||||||
|
"version": "7.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||||
|
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint-plugin-vue/node_modules/type-fest": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "(MIT OR CC0-1.0)",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eslint-scope": {
|
"node_modules/eslint-scope": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||||
@@ -5533,6 +5640,19 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint/node_modules/@eslint/core": {
|
||||||
|
"version": "0.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.11.0.tgz",
|
||||||
|
"integrity": "sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/json-schema": "^7.0.15"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eslint/node_modules/eslint-scope": {
|
"node_modules/eslint/node_modules/eslint-scope": {
|
||||||
"version": "8.2.0",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
|
||||||
@@ -10331,6 +10451,102 @@
|
|||||||
"npm": ">= 3.0.0"
|
"npm": ">= 3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-eslint-parser": {
|
||||||
|
"version": "9.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz",
|
||||||
|
"integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^4.3.4",
|
||||||
|
"eslint-scope": "^7.1.1",
|
||||||
|
"eslint-visitor-keys": "^3.3.0",
|
||||||
|
"espree": "^9.3.1",
|
||||||
|
"esquery": "^1.4.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"semver": "^7.3.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.17.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/mysticatea"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue-eslint-parser/node_modules/eslint-scope": {
|
||||||
|
"version": "7.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
|
||||||
|
"integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"esrecurse": "^4.3.0",
|
||||||
|
"estraverse": "^5.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": {
|
||||||
|
"version": "3.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
|
||||||
|
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue-eslint-parser/node_modules/espree": {
|
||||||
|
"version": "9.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
|
||||||
|
"integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"acorn": "^8.9.0",
|
||||||
|
"acorn-jsx": "^5.3.2",
|
||||||
|
"eslint-visitor-keys": "^3.4.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue-eslint-parser/node_modules/estraverse": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue-eslint-parser/node_modules/semver": {
|
||||||
|
"version": "7.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||||
|
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue-hot-reload-api": {
|
"node_modules/vue-hot-reload-api": {
|
||||||
"version": "2.3.4",
|
"version": "2.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",
|
||||||
@@ -10751,6 +10967,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/xml-name-validator": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/xmlbuilder": {
|
"node_modules/xmlbuilder": {
|
||||||
"version": "15.1.1",
|
"version": "15.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
|
||||||
@@ -10887,6 +11113,23 @@
|
|||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zrender": {
|
||||||
|
"version": "5.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz",
|
||||||
|
"integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "2.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zrender/node_modules/tslib": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "0BSD"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,12 +41,15 @@
|
|||||||
"copy-webpack-plugin": "^12.0.2",
|
"copy-webpack-plugin": "^12.0.2",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "^7.1.2",
|
"css-loader": "^7.1.2",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"default-passive-events": "^2.0.0",
|
"default-passive-events": "^2.0.0",
|
||||||
|
"echarts": "^5.6.0",
|
||||||
"electron": "^34.0.2",
|
"electron": "^34.0.2",
|
||||||
"electron-builder": "^25.1.8",
|
"electron-builder": "^25.1.8",
|
||||||
"element-ui": "^2.15.14",
|
"element-ui": "^2.15.14",
|
||||||
"eslint": "^9.19.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
|
"eslint-plugin-vue": "^9.32.0",
|
||||||
"html-webpack-plugin": "^5.6.3",
|
"html-webpack-plugin": "^5.6.3",
|
||||||
"mini-css-extract-plugin": "^2.9.2",
|
"mini-css-extract-plugin": "^2.9.2",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
|
|||||||
+22
-1
@@ -16,6 +16,10 @@ import VueLazyload from 'vue-lazyload';
|
|||||||
import VueI18n from 'vue-i18n';
|
import VueI18n from 'vue-i18n';
|
||||||
import { DataTables } from 'vue-data-tables';
|
import { DataTables } from 'vue-data-tables';
|
||||||
import ElementUI from 'element-ui';
|
import ElementUI from 'element-ui';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import duration from 'dayjs/plugin/duration';
|
||||||
|
import utc from 'dayjs/plugin/utc';
|
||||||
|
import timezone from 'dayjs/plugin/timezone';
|
||||||
import * as workerTimers from 'worker-timers';
|
import * as workerTimers from 'worker-timers';
|
||||||
import 'default-passive-events';
|
import 'default-passive-events';
|
||||||
|
|
||||||
@@ -33,6 +37,7 @@ import _vrcxJsonStorage from './classes/vrcxJsonStorage.js';
|
|||||||
|
|
||||||
// tabs
|
// tabs
|
||||||
import ModerationTab from './views/tabs/Moderation.vue';
|
import ModerationTab from './views/tabs/Moderation.vue';
|
||||||
|
import ChartsTab from './views/tabs/Charts.vue';
|
||||||
|
|
||||||
// components
|
// components
|
||||||
import SimpleSwitch from './components/settings/SimpleSwitch.vue';
|
import SimpleSwitch from './components/settings/SimpleSwitch.vue';
|
||||||
@@ -109,6 +114,13 @@ console.log(`isLinux: ${LINUX}`);
|
|||||||
});
|
});
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
|
// #region | date utility library
|
||||||
|
// - dayjs plugin init
|
||||||
|
dayjs.extend(duration);
|
||||||
|
dayjs.extend(utc);
|
||||||
|
dayjs.extend(timezone);
|
||||||
|
// #endregion
|
||||||
|
|
||||||
// everything in this program is global stored in $app, I hate it, it is what it is
|
// everything in this program is global stored in $app, I hate it, it is what it is
|
||||||
let $app = {};
|
let $app = {};
|
||||||
const API = new _apiInit($app);
|
const API = new _apiInit($app);
|
||||||
@@ -164,6 +176,7 @@ console.log(`isLinux: ${LINUX}`);
|
|||||||
components: {
|
components: {
|
||||||
// tabs
|
// tabs
|
||||||
ModerationTab,
|
ModerationTab,
|
||||||
|
ChartsTab,
|
||||||
|
|
||||||
// components
|
// components
|
||||||
// - settings
|
// - settings
|
||||||
@@ -173,6 +186,12 @@ console.log(`isLinux: ${LINUX}`);
|
|||||||
// - sidebar(friendsListSidebar)
|
// - sidebar(friendsListSidebar)
|
||||||
GroupsSidebar
|
GroupsSidebar
|
||||||
},
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
API,
|
||||||
|
showUserDialog: this.showUserDialog
|
||||||
|
};
|
||||||
|
},
|
||||||
el: '#x-app',
|
el: '#x-app',
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await this.initLanguage();
|
await this.initLanguage();
|
||||||
@@ -4824,7 +4843,9 @@ console.log(`isLinux: ${LINUX}`);
|
|||||||
});
|
});
|
||||||
worldName = args.ref.name;
|
worldName = args.ref.name;
|
||||||
}
|
}
|
||||||
} catch (err) {}
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
return worldName;
|
return worldName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,8 @@ export default class extends baseClass {
|
|||||||
link: {
|
link: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
}
|
},
|
||||||
|
isOpenPreviousInstanceInfoDialog: Boolean
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -177,7 +178,14 @@ export default class extends baseClass {
|
|||||||
API.$emit('SHOW_WORLD_DIALOG_SHORTNAME', this.hint);
|
API.$emit('SHOW_WORLD_DIALOG_SHORTNAME', this.hint);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
API.$emit('SHOW_WORLD_DIALOG', instanceId);
|
if (this.isOpenPreviousInstanceInfoDialog) {
|
||||||
|
this.$emit(
|
||||||
|
'open-previous-instance-info-dialog',
|
||||||
|
instanceId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
API.$emit('SHOW_WORLD_DIALOG', instanceId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showGroupDialog() {
|
showGroupDialog() {
|
||||||
|
|||||||
@@ -40,13 +40,13 @@ export default {
|
|||||||
return obj;
|
return obj;
|
||||||
},
|
},
|
||||||
|
|
||||||
timeToText(sec) {
|
timeToText(sec, isNeedSeconds = false) {
|
||||||
var n = Number(sec);
|
let n = Number(sec);
|
||||||
if (isNaN(n)) {
|
if (isNaN(n)) {
|
||||||
return this.escapeTag(sec);
|
return this.escapeTag(sec);
|
||||||
}
|
}
|
||||||
n = Math.floor(n / 1000);
|
n = Math.floor(n / 1000);
|
||||||
var arr = [];
|
const arr = [];
|
||||||
if (n < 0) {
|
if (n < 0) {
|
||||||
n = -n;
|
n = -n;
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@ export default {
|
|||||||
arr.push(`${Math.floor(n / 60)}m`);
|
arr.push(`${Math.floor(n / 60)}m`);
|
||||||
n %= 60;
|
n %= 60;
|
||||||
}
|
}
|
||||||
if (arr.length === 0 && n < 60) {
|
if (isNeedSeconds || (arr.length === 0 && n < 60)) {
|
||||||
arr.push(`${n}s`);
|
arr.push(`${n}s`);
|
||||||
}
|
}
|
||||||
return arr.join(' ');
|
return arr.join(' ');
|
||||||
|
|||||||
@@ -0,0 +1,421 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="options-container flex-between" style="margin-top: 0">
|
||||||
|
<span style="margin-top: 10px">Instance Activity</span>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="selectedDate"
|
||||||
|
type="date"
|
||||||
|
:clearable="false"
|
||||||
|
align="right"
|
||||||
|
:picker-options="{
|
||||||
|
disabledDate: (time) => getDatePickerDisabledDate(time)
|
||||||
|
}"
|
||||||
|
@change="handleSelectDate"
|
||||||
|
></el-date-picker>
|
||||||
|
</div>
|
||||||
|
<div style="position: relative">
|
||||||
|
<el-statistic title="Total Online Time">
|
||||||
|
<template #formatter>
|
||||||
|
<span :style="isDarkMode ? 'color:rgb(120,120,120)' : ''">{{ totalOnlineTime }}</span>
|
||||||
|
</template>
|
||||||
|
</el-statistic>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ref="activityChartRef" style="width: 100%"></div>
|
||||||
|
<div v-if="!isLoading && activityData.length === 0" class="nodata">
|
||||||
|
<span>No data here, try another day</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<transition name="el-fade-in-linear">
|
||||||
|
<div v-show="!isLoading && activityData.length !== 0" class="divider"><el-divider>·</el-divider></div>
|
||||||
|
</transition>
|
||||||
|
<instance-activity-detail
|
||||||
|
v-for="arr in activityDetailData"
|
||||||
|
:key="arr[0].location"
|
||||||
|
ref="activityDetailChartRef"
|
||||||
|
:activity-data="activityData"
|
||||||
|
:activity-detail-data="arr"
|
||||||
|
:is-dark-mode="isDarkMode"
|
||||||
|
style="width: 100%"
|
||||||
|
@open-previous-instance-info-dialog="$emit('open-previous-instance-info-dialog', $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import database from '../../repository/database';
|
||||||
|
import utils from '../../classes/utils';
|
||||||
|
import InstanceActivityDetail from './InstanceActivityDetail.vue';
|
||||||
|
|
||||||
|
let echarts = null;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'InstanceActivity',
|
||||||
|
components: {
|
||||||
|
InstanceActivityDetail
|
||||||
|
},
|
||||||
|
inject: ['API'],
|
||||||
|
props: {
|
||||||
|
getWorldName: Function,
|
||||||
|
isDarkMode: Boolean
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
echartsInstance: null,
|
||||||
|
resizeObserver: null,
|
||||||
|
intersectionObservers: [],
|
||||||
|
selectedDate: dayjs().add(-1, 'day'),
|
||||||
|
activityData: [],
|
||||||
|
activityDetailData: [],
|
||||||
|
// previousDarkMode: this.isDarkMode,
|
||||||
|
allDateOfActivity: null,
|
||||||
|
firstDateOfActivity: null,
|
||||||
|
worldNameArray: [],
|
||||||
|
isLoading: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
totalOnlineTime() {
|
||||||
|
return utils.timeToText(
|
||||||
|
this.activityData.reduce((acc, item) => acc + item.time, 0),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
activated() {
|
||||||
|
// first time also call activated
|
||||||
|
if (!this.echartsInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// if (this.isDarkMode === this.previousDarkMode) {
|
||||||
|
// when tab activated, play animation
|
||||||
|
this.echartsInstance.clear();
|
||||||
|
this.initEcharts();
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
deactivated() {
|
||||||
|
// prevent switch tab play resize animation
|
||||||
|
this.resizeObserver.disconnect();
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
this.echartsInstance.resize({
|
||||||
|
width: entry.contentRect.width,
|
||||||
|
animation: {
|
||||||
|
duration: 300
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
try {
|
||||||
|
const [echartsModule] = await Promise.all([
|
||||||
|
// lazy load echarts
|
||||||
|
// reduce the VRCX initial screen load times
|
||||||
|
// TODO: export lazy load func to a single file
|
||||||
|
import('echarts').catch((error) => {
|
||||||
|
console.error('lazy load echarts failed', error);
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
this.getActivityData()
|
||||||
|
]);
|
||||||
|
if (echartsModule) {
|
||||||
|
echarts = echartsModule;
|
||||||
|
}
|
||||||
|
if (this.activityData.length && echarts) {
|
||||||
|
// actvity data is ready, but world name data isn't ready
|
||||||
|
// so init echarts with empty data, redcuce the render time of init screen
|
||||||
|
// TODO: move to created lifecycle, init screen faster
|
||||||
|
this.initEcharts(true);
|
||||||
|
this.getAllDateOfActivity();
|
||||||
|
this.getWorldNameData();
|
||||||
|
} else {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('error in mounted', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// reload data
|
||||||
|
async handleSelectDate() {
|
||||||
|
this.isLoading = true;
|
||||||
|
await this.getActivityData();
|
||||||
|
this.getWorldNameData();
|
||||||
|
},
|
||||||
|
initEcharts(isFirstTime = false) {
|
||||||
|
const chartsHeight = this.activityData.length * 40 + 200;
|
||||||
|
const chartDom = this.$refs.activityChartRef;
|
||||||
|
if (!this.echartsInstance) {
|
||||||
|
this.echartsInstance = echarts.init(chartDom, `${this.isDarkMode ? 'dark' : null}`, {
|
||||||
|
height: chartsHeight
|
||||||
|
});
|
||||||
|
this.resizeObserver.observe(chartDom);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.echartsInstance.resize({
|
||||||
|
height: chartsHeight,
|
||||||
|
animation: {
|
||||||
|
duration: 300
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.echartsInstance.setOption(this.getNewOption(isFirstTime), { lazyUpdate: true });
|
||||||
|
this.echartsInstance.on('click', 'yAxis', this.hanleClickYAxisLable);
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
hanleClickYAxisLable(params) {
|
||||||
|
const detailDataIdx = this.activityDetailData.findIndex(
|
||||||
|
(arr) => arr[0]?.location === this.activityData[params?.dataIndex]?.location
|
||||||
|
);
|
||||||
|
if (detailDataIdx !== -1) {
|
||||||
|
this.$refs.activityDetailChartRef[detailDataIdx].$el.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'start'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDatePickerDisabledDate(time) {
|
||||||
|
if (time > Date.now() || time < this.firstDateOfActivity) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !this.allDateOfActivity.has(dayjs(time).format('YYYY-MM-DD'));
|
||||||
|
},
|
||||||
|
async getAllDateOfActivity() {
|
||||||
|
const utcDateStrings = await database.getDateOfInstanceActivity();
|
||||||
|
|
||||||
|
const uniqueDates = new Set();
|
||||||
|
this.firstDateOfActivity = dayjs.utc(utcDateStrings[0]).startOf('day');
|
||||||
|
|
||||||
|
for (const utcString of utcDateStrings) {
|
||||||
|
const formattedDate = dayjs.utc(utcString).tz().format('YYYY-MM-DD');
|
||||||
|
uniqueDates.add(formattedDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.allDateOfActivity = uniqueDates;
|
||||||
|
},
|
||||||
|
async getActivityData() {
|
||||||
|
const localStartDate = dayjs.tz(this.selectedDate).startOf('day').toISOString();
|
||||||
|
const localEndDate = dayjs.tz(this.selectedDate).endOf('day').toISOString();
|
||||||
|
const dbData = await database.getInstanceActivity(localStartDate, localEndDate);
|
||||||
|
|
||||||
|
const transformData = (item) => ({
|
||||||
|
...item,
|
||||||
|
joinTime: dayjs(item.created_at).subtract(item.time, 'millisecond'),
|
||||||
|
leaveTime: dayjs(item.created_at),
|
||||||
|
time: item.time < 0 ? 0 : item.time
|
||||||
|
});
|
||||||
|
|
||||||
|
this.activityData = dbData.currentUserData.map(transformData);
|
||||||
|
|
||||||
|
// FIXME: some detail data missing current user activity
|
||||||
|
this.activityDetailData = Array.from(dbData.detailData.values()).map((arr) =>
|
||||||
|
arr.map(transformData).sort((a, b) => {
|
||||||
|
const timeDiff = Math.abs(a.joinTime.diff(b.joinTime, 'second'));
|
||||||
|
// recording delay, under 2s is considered the same time entry, beautify the chart
|
||||||
|
if (timeDiff < 2) {
|
||||||
|
return a.leaveTime - b.leaveTime;
|
||||||
|
}
|
||||||
|
return a.joinTime - b.joinTime;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.handleIntersectionObserver();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleIntersectionObserver() {
|
||||||
|
this.$refs.activityDetailChartRef.forEach((child, index) => {
|
||||||
|
const observer = new IntersectionObserver(this.handleIntersection.bind(this, index));
|
||||||
|
observer.observe(child.$el);
|
||||||
|
this.intersectionObservers[index] = observer;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleIntersection(index, entries) {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
this.$refs.activityDetailChartRef[index].initEcharts();
|
||||||
|
this.intersectionObservers[index].unobserve(entry.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async getWorldNameData() {
|
||||||
|
this.worldNameArray = await Promise.all(
|
||||||
|
this.activityData.map(async (item) => {
|
||||||
|
try {
|
||||||
|
return await this.getWorldName(item.location);
|
||||||
|
} catch {
|
||||||
|
// TODO: no notification
|
||||||
|
console.error('getWorldName failed location', item.location);
|
||||||
|
return 'Unknown world';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
if (this.worldNameArray && this.echartsInstance) {
|
||||||
|
this.initEcharts();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getNewOption(isFirstTime) {
|
||||||
|
const getTooltip = (params) => {
|
||||||
|
const activityData = this.activityData;
|
||||||
|
const param = params[1];
|
||||||
|
|
||||||
|
if (!activityData || !activityData[param.dataIndex]) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const instanceData = activityData[param.dataIndex];
|
||||||
|
|
||||||
|
const formattedLeftDateTime = dayjs(instanceData.leaveTime).format('HH:mm:ss');
|
||||||
|
const formattedJoinDateTime = dayjs(instanceData.joinTime).format('HH:mm:ss');
|
||||||
|
|
||||||
|
const timeString = utils.timeToText(param.data, true);
|
||||||
|
const color = param.color;
|
||||||
|
const name = param.name;
|
||||||
|
const location = utils.parseLocation(instanceData.location);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div style="display: flex; align-items: center;">
|
||||||
|
<div style="width: 10px; height: 55px; background-color: ${color}; margin-right: 5px;"></div>
|
||||||
|
<div>
|
||||||
|
<div>${name} #${location.instanceName} ${location.accessTypeName}</div>
|
||||||
|
<div>${formattedJoinDateTime} - ${formattedLeftDateTime}</div>
|
||||||
|
<div>${timeString}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const echartsOption = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow'
|
||||||
|
},
|
||||||
|
formatter: getTooltip
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 50,
|
||||||
|
left: 160,
|
||||||
|
right: 90
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'category',
|
||||||
|
axisLabel: {
|
||||||
|
interval: 0,
|
||||||
|
formatter: (value) => (value.length > 20 ? `${value.slice(0, 20)}...` : value)
|
||||||
|
},
|
||||||
|
inverse: true,
|
||||||
|
data: this.worldNameArray,
|
||||||
|
triggerEvent: true
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
// axisLabel max 24:00
|
||||||
|
max: 24 * 60 * 60 * 1000,
|
||||||
|
// axisLabel interval 3hr
|
||||||
|
interval: 3 * 60 * 60 * 1000,
|
||||||
|
axisLine: { show: true },
|
||||||
|
axisLabel: {
|
||||||
|
formatter: (value) =>
|
||||||
|
value === 24 * 60 * 60 * 1000 ? '24:00' : dayjs(value).utc().format('HH:mm')
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { type: 'dashed' } }
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'Placeholder',
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'Total',
|
||||||
|
itemStyle: {
|
||||||
|
borderColor: 'transparent',
|
||||||
|
color: 'transparent'
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
itemStyle: {
|
||||||
|
borderColor: 'transparent',
|
||||||
|
color: 'transparent'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: isFirstTime
|
||||||
|
? []
|
||||||
|
: this.activityData.map((item, idx) => {
|
||||||
|
if (idx === 0) {
|
||||||
|
const midnight = dayjs.tz(this.selectedDate).startOf('day');
|
||||||
|
if (midnight.isAfter(item.joinTime)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item.joinTime - dayjs.tz(this.selectedDate).startOf('day');
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Time',
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'Total',
|
||||||
|
colorBy: 'data',
|
||||||
|
barWidth: 30,
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 2,
|
||||||
|
shadowBlur: 2,
|
||||||
|
shadowOffsetX: 0.7,
|
||||||
|
shadowOffsetY: 0.5
|
||||||
|
},
|
||||||
|
data: isFirstTime
|
||||||
|
? []
|
||||||
|
: this.activityData.map((item, idx) => {
|
||||||
|
// If the joinTime of the first data is on the previous day,
|
||||||
|
// and the data traverses midnight, the duration starts at midnight
|
||||||
|
if (idx === 0) {
|
||||||
|
const midnight = dayjs.tz(this.selectedDate).startOf('day');
|
||||||
|
if (midnight.isAfter(item.joinTime)) {
|
||||||
|
return item.leaveTime - dayjs.tz(midnight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item.time;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.isDarkMode) {
|
||||||
|
echartsOption.backgroundColor = 'rgba(0, 0, 0, 0)';
|
||||||
|
}
|
||||||
|
return echartsOption;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.flex-between {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.nodata {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 200px;
|
||||||
|
color: #5c5c5c;
|
||||||
|
}
|
||||||
|
.divider {
|
||||||
|
padding: 0 400px;
|
||||||
|
transition: top 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-date-editor.el-input,
|
||||||
|
.el-date-editor.el-input__inner {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
.el-divider__text {
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div style="height: 25px; margin-top: 60px">
|
||||||
|
<transition name="el-fade-in-linear">
|
||||||
|
<location
|
||||||
|
v-show="!isLoading"
|
||||||
|
class="location"
|
||||||
|
:location="activityDetailData[0].location"
|
||||||
|
is-open-previous-instance-info-dialog
|
||||||
|
@open-previous-instance-info-dialog="$emit('open-previous-instance-info-dialog', $event)"
|
||||||
|
></location>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ref="activityDetailChart"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import utils from '../../classes/utils';
|
||||||
|
|
||||||
|
let echarts = null;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'InstanceActivityDetail',
|
||||||
|
inject: ['API'],
|
||||||
|
props: {
|
||||||
|
activityDetailData: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
activityData: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isDarkMode: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoading: true,
|
||||||
|
echartsInstance: null,
|
||||||
|
resizeObserver: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
startTimeStamp() {
|
||||||
|
return this.activityData
|
||||||
|
.find((item) => item.location === this.activityDetailData[0].location)
|
||||||
|
?.joinTime.valueOf();
|
||||||
|
},
|
||||||
|
endTimeStamp() {
|
||||||
|
return this.activityData
|
||||||
|
.findLast((item) => item.location === this.activityDetailData[0].location)
|
||||||
|
?.leaveTime.valueOf();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
this.echartsInstance.resize({
|
||||||
|
width: entry.contentRect.width,
|
||||||
|
animation: {
|
||||||
|
duration: 300
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deactivated() {
|
||||||
|
// prevent switch tab play resize animation
|
||||||
|
this.resizeObserver.disconnect();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async initEcharts() {
|
||||||
|
// TODO: unnecessary import, import from individual js file
|
||||||
|
await import('echarts').then((echartsModule) => {
|
||||||
|
echarts = echartsModule;
|
||||||
|
});
|
||||||
|
const chartDom = this.$refs.activityDetailChart;
|
||||||
|
if (!this.echartsInstance) {
|
||||||
|
this.echartsInstance = echarts.init(chartDom, `${this.isDarkMode ? 'dark' : null}`, {
|
||||||
|
height: this.activityDetailData.length * 40 + 200,
|
||||||
|
useDirtyRect: this.activityDetailData.length > 30
|
||||||
|
});
|
||||||
|
this.resizeObserver.observe(chartDom);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.echartsInstance.resize({
|
||||||
|
height: this.activityDetailData.length * 40 + 200,
|
||||||
|
animation: {
|
||||||
|
duration: 300
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.echartsInstance.setOption(this.getNewOption(), { lazyUpdate: true });
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
}, 200);
|
||||||
|
},
|
||||||
|
getNewOption() {
|
||||||
|
const getTooltip = (params) => {
|
||||||
|
const activityDetailData = this.activityDetailData;
|
||||||
|
const param = params[1];
|
||||||
|
|
||||||
|
if (!activityDetailData || !activityDetailData[param.dataIndex]) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const instanceData = activityDetailData[param.dataIndex];
|
||||||
|
|
||||||
|
const formattedLeftDateTime = dayjs(instanceData.leaveTime).format('HH:mm:ss');
|
||||||
|
const formattedJoinDateTime = dayjs(instanceData.joinTime).format('HH:mm:ss');
|
||||||
|
|
||||||
|
const timeString = utils.timeToText(instanceData.time, true);
|
||||||
|
const color = param.color;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div style="display: flex; align-items: center;">
|
||||||
|
<div style="width: 10px; height: 55px; background-color: ${color}; margin-right: 5px;"></div>
|
||||||
|
<div>
|
||||||
|
<div>${instanceData.display_name}</div>
|
||||||
|
<div>${formattedJoinDateTime} - ${formattedLeftDateTime}</div>
|
||||||
|
<div>${timeString}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const echartsOption = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow'
|
||||||
|
},
|
||||||
|
formatter: getTooltip
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 60,
|
||||||
|
left: 160,
|
||||||
|
right: 90
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'category',
|
||||||
|
axisLabel: {
|
||||||
|
interval: 0,
|
||||||
|
formatter: (value) => (value.length > 20 ? `${value.slice(0, 20)}...` : value)
|
||||||
|
},
|
||||||
|
inverse: true,
|
||||||
|
data: this.activityDetailData.map((item) => item.display_name)
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
max: this.endTimeStamp - this.startTimeStamp,
|
||||||
|
axisLine: { show: true },
|
||||||
|
axisLabel: {
|
||||||
|
formatter: (value) => dayjs(value + this.startTimeStamp).format('HH:mm')
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { type: 'dashed' } }
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'Placeholder',
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'Total',
|
||||||
|
itemStyle: {
|
||||||
|
borderColor: 'transparent',
|
||||||
|
color: 'transparent'
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
itemStyle: {
|
||||||
|
borderColor: 'transparent',
|
||||||
|
color: 'transparent'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: this.activityDetailData.map((item) => item.joinTime.valueOf() - this.startTimeStamp)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Time',
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'Total',
|
||||||
|
colorBy: 'data',
|
||||||
|
barWidth: 30,
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 2,
|
||||||
|
shadowBlur: 2,
|
||||||
|
shadowOffsetX: 0.7,
|
||||||
|
shadowOffsetY: 0.5
|
||||||
|
},
|
||||||
|
data: this.activityDetailData.map((item) => item.time)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.isDarkMode) {
|
||||||
|
echartsOption.backgroundColor = 'rgba(0, 0, 0, 0)';
|
||||||
|
}
|
||||||
|
return echartsOption;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.location {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+11
-3
@@ -51,6 +51,7 @@ html
|
|||||||
+menuitem('moderation', "{{ $t('nav_tooltip.moderation') }}", 'el-icon-finished')
|
+menuitem('moderation', "{{ $t('nav_tooltip.moderation') }}", 'el-icon-finished')
|
||||||
+menuitem('notification', "{{ $t('nav_tooltip.notification') }}", 'el-icon-bell')
|
+menuitem('notification', "{{ $t('nav_tooltip.notification') }}", 'el-icon-bell')
|
||||||
+menuitem('friendsList', "{{ $t('nav_tooltip.friend_list') }}", 'el-icon-s-management')
|
+menuitem('friendsList', "{{ $t('nav_tooltip.friend_list') }}", 'el-icon-s-management')
|
||||||
|
+menuitem('charts', "{{ $t('nav_tooltip.charts') }}", 'el-icon-data-analysis')
|
||||||
+menuitem('profile', "{{ $t('nav_tooltip.profile') }}", 'el-icon-user')
|
+menuitem('profile', "{{ $t('nav_tooltip.profile') }}", 'el-icon-user')
|
||||||
+menuitem('settings', "{{ $t('nav_tooltip.settings') }}", 'el-icon-s-tools')
|
+menuitem('settings', "{{ $t('nav_tooltip.settings') }}", 'el-icon-s-tools')
|
||||||
|
|
||||||
@@ -83,10 +84,9 @@ html
|
|||||||
//- moderation
|
//- moderation
|
||||||
moderation-tab(
|
moderation-tab(
|
||||||
v-if='$refs.menu?.activeIndex === "moderation"'
|
v-if='$refs.menu?.activeIndex === "moderation"'
|
||||||
:Api='API'
|
|
||||||
:table-data='playerModerationTable'
|
:table-data='playerModerationTable'
|
||||||
:show-user-dialog='showUserDialog'
|
:shift-held='shiftHeld'
|
||||||
:shift-held='shiftHeld')
|
:hide-tooltips='hideTooltips')
|
||||||
|
|
||||||
//- notification
|
//- notification
|
||||||
include ./mixins/tabs/notifications.pug
|
include ./mixins/tabs/notifications.pug
|
||||||
@@ -100,6 +100,14 @@ html
|
|||||||
include ./mixins/tabs/friendsList.pug
|
include ./mixins/tabs/friendsList.pug
|
||||||
+friendsListTab
|
+friendsListTab
|
||||||
|
|
||||||
|
//- charts
|
||||||
|
keep-alive
|
||||||
|
charts-tab(
|
||||||
|
v-if='$refs.menu?.activeIndex === "charts"'
|
||||||
|
:get-world-name='getWorldName'
|
||||||
|
:is-dark-mode='isDarkMode'
|
||||||
|
@open-previous-instance-info-dialog='showPreviousInstanceInfoDialog')
|
||||||
|
|
||||||
//- settings
|
//- settings
|
||||||
include ./mixins/tabs/settings.pug
|
include ./mixins/tabs/settings.pug
|
||||||
+settingsTab
|
+settingsTab
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"moderation": "Moderation",
|
"moderation": "Moderation",
|
||||||
"notification": "Notification",
|
"notification": "Notification",
|
||||||
"friend_list": "Friend List",
|
"friend_list": "Friend List",
|
||||||
|
"charts": "Charts",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
"settings": "Settings"
|
"settings": "Settings"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -398,3 +398,36 @@ mixin openSourceSoftwareNotice
|
|||||||
OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
|
ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
|
||||||
DEALINGS IN THE FONT SOFTWARE.
|
DEALINGS IN THE FONT SOFTWARE.
|
||||||
|
div(style='margin-top: 15px')
|
||||||
|
p(style='font-weight: bold') Apache ECharts
|
||||||
|
pre(style='font-size: 12px; white-space: pre-line').
|
||||||
|
Apache License 2.0
|
||||||
|
|
||||||
|
Copyright 2017-2025 The Apache Software Foundation
|
||||||
|
|
||||||
|
This product includes software developed at
|
||||||
|
The Apache Software Foundation (https://www.apache.org/).
|
||||||
|
div(style='margin-top: 15px')
|
||||||
|
p(style='font-weight: bold') dayjs
|
||||||
|
pre(style='font-size: 12px; white-space: pre-line').
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018-present, iamkun
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
mixin friendsListSidebar
|
mixin friendsListSidebar
|
||||||
#aside.x-aside-container(v-show='$refs.menu && $refs.menu.activeIndex !== \'friendsList\'')
|
#aside.x-aside-container(
|
||||||
|
v-show='$refs.menu && !($refs.menu.activeIndex == "friendsList" || $refs.menu.activeIndex == "charts") ')
|
||||||
div(style='display: flex; align-items: baseline')
|
div(style='display: flex; align-items: baseline')
|
||||||
el-select(
|
el-select(
|
||||||
v-model='quickSearch'
|
v-model='quickSearch'
|
||||||
|
|||||||
@@ -2772,6 +2772,80 @@ class Database {
|
|||||||
);
|
);
|
||||||
return instances;
|
return instances;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} startDate: utc string of startOfDay
|
||||||
|
* @param {string} endDate: utc string endOfDay
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async getInstanceActivity(startDate, endDate) {
|
||||||
|
const currentUserData = [];
|
||||||
|
const detailData = new Map();
|
||||||
|
await sqliteService.execute(
|
||||||
|
(row) => {
|
||||||
|
const rowData = {
|
||||||
|
id: row[0],
|
||||||
|
created_at: row[1],
|
||||||
|
type: row[2],
|
||||||
|
display_name: row[3],
|
||||||
|
location: row[4],
|
||||||
|
user_id: row[5],
|
||||||
|
time: row[6]
|
||||||
|
};
|
||||||
|
|
||||||
|
// skip dirty data
|
||||||
|
if (!rowData.location || rowData.location === 'traveling') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rowData.user_id === Database.userId) {
|
||||||
|
currentUserData.push(rowData);
|
||||||
|
}
|
||||||
|
const instanceData = detailData.get(rowData.location);
|
||||||
|
|
||||||
|
detailData.set(rowData.location, [
|
||||||
|
...(instanceData || []),
|
||||||
|
rowData
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
`SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
gamelog_join_leave
|
||||||
|
WHERE type = "OnPlayerLeft"
|
||||||
|
AND (
|
||||||
|
strftime('%Y-%m-%dT%H:%M:%SZ', created_at, '-' || (time * 1.0 / 1000) || ' seconds') BETWEEN @utc_start_date AND @utc_end_date
|
||||||
|
OR created_at BETWEEN @utc_start_date AND @utc_end_date
|
||||||
|
);`,
|
||||||
|
{
|
||||||
|
'@utc_start_date': startDate,
|
||||||
|
'@utc_end_date': endDate
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return { currentUserData, detailData };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the All Date of Instance Activity for the current user
|
||||||
|
* @returns {Promise<null>}
|
||||||
|
*/
|
||||||
|
async getDateOfInstanceActivity() {
|
||||||
|
let result = [];
|
||||||
|
await sqliteService.execute(
|
||||||
|
(row) => {
|
||||||
|
result.push(row[0]);
|
||||||
|
},
|
||||||
|
`SELECT created_at
|
||||||
|
FROM gamelog_join_leave
|
||||||
|
WHERE user_id = @userId`,
|
||||||
|
{
|
||||||
|
'@userId': Database.userId
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var self = new Database();
|
var self = new Database();
|
||||||
|
|||||||
@@ -432,3 +432,10 @@ button {
|
|||||||
padding: 2px 2px;
|
padding: 2px 2px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
.el-divider {
|
||||||
|
background-color: #606266;
|
||||||
|
}
|
||||||
|
.el-divider__text {
|
||||||
|
background: #222;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|||||||
@@ -696,3 +696,11 @@ i[class='el-icon-star-off']:not(.el-menu-item div.el-tooltip i) {
|
|||||||
.el-color-picker__panel {
|
.el-color-picker__panel {
|
||||||
background-color: var(--dv_bg-top);
|
background-color: var(--dv_bg-top);
|
||||||
}
|
}
|
||||||
|
.el-divider {
|
||||||
|
background-color: #606266;
|
||||||
|
}
|
||||||
|
.el-divider__text {
|
||||||
|
background: var(--dv_bg-top);
|
||||||
|
color: #efefef;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -318,3 +318,10 @@ path[stroke='#e5e9f2'] {
|
|||||||
border: transparent;
|
border: transparent;
|
||||||
background-color: #333 !important;
|
background-color: #333 !important;
|
||||||
}
|
}
|
||||||
|
.el-divider {
|
||||||
|
background-color: #606266;
|
||||||
|
}
|
||||||
|
.el-divider__text {
|
||||||
|
background: var(--farback);
|
||||||
|
color: #efefef;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2012,3 +2012,10 @@ i.x-user-status {
|
|||||||
.el-dialog__body .el-tag--mini {
|
.el-dialog__body .el-tag--mini {
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
}
|
}
|
||||||
|
.el-divider {
|
||||||
|
background-color: #606266;
|
||||||
|
}
|
||||||
|
.el-divider__text {
|
||||||
|
background: rgba(var(--md-sys-color-background));
|
||||||
|
color: #efefef;
|
||||||
|
}
|
||||||
|
|||||||
@@ -366,3 +366,10 @@ input[type='checkbox']:checked + .el-switch__core {
|
|||||||
border: transparent;
|
border: transparent;
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
}
|
}
|
||||||
|
.el-divider {
|
||||||
|
background-color: #606266;
|
||||||
|
}
|
||||||
|
.el-divider__text {
|
||||||
|
background: var(--bg);
|
||||||
|
color: #efefef;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<div class="x-container" id="chart">
|
||||||
|
<div class="options-container" style="margin-top: 0">
|
||||||
|
<span class="header">Charts</span>
|
||||||
|
</div>
|
||||||
|
<instance-activity
|
||||||
|
:get-world-name="getWorldName"
|
||||||
|
:is-dark-mode="isDarkMode"
|
||||||
|
@open-previous-instance-info-dialog="$emit('open-previous-instance-info-dialog', $event)"
|
||||||
|
id="instance-activity"
|
||||||
|
></instance-activity>
|
||||||
|
<el-backtop target="#chart" :right="30" :bottom="30"></el-backtop>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import InstanceActivity from '../../components/charts/InstanceActivity.vue';
|
||||||
|
export default {
|
||||||
|
name: 'ChartsTab',
|
||||||
|
inject: ['API'],
|
||||||
|
props: {
|
||||||
|
getWorldName: Function,
|
||||||
|
isDarkMode: Boolean
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
InstanceActivity
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
:filters="filters"
|
:filters="filters"
|
||||||
:tableProps="tableProps"
|
:tableProps="tableProps"
|
||||||
:paginationProps="paginationProps"
|
:paginationProps="paginationProps"
|
||||||
v-loading="Api.isPlayerModerationsLoading"
|
v-loading="API.isPlayerModerationsLoading"
|
||||||
>
|
>
|
||||||
<template slot="tool">
|
<template slot="tool">
|
||||||
<div class="tool-slot">
|
<div class="tool-slot">
|
||||||
@@ -37,48 +37,30 @@
|
|||||||
>
|
>
|
||||||
<el-button
|
<el-button
|
||||||
type="default"
|
type="default"
|
||||||
:loading="Api.isPlayerModerationsLoading"
|
:loading="API.isPlayerModerationsLoading"
|
||||||
@click="Api.refreshPlayerModerations()"
|
@click="API.refreshPlayerModerations()"
|
||||||
icon="el-icon-refresh"
|
icon="el-icon-refresh"
|
||||||
circle
|
circle
|
||||||
/>
|
/>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-table-column
|
<el-table-column :label="$t('table.moderation.date')" prop="created" sortable="custom" width="120">
|
||||||
:label="$t('table.moderation.date')"
|
|
||||||
prop="created"
|
|
||||||
sortable="custom"
|
|
||||||
width="120"
|
|
||||||
>
|
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-tooltip placement="right">
|
<el-tooltip placement="right">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<span>{{
|
<span>{{ scope.row.created | formatDate('long') }}</span>
|
||||||
scope.row.created | formatDate('long')
|
|
||||||
}}</span>
|
|
||||||
</template>
|
</template>
|
||||||
<span>{{
|
<span>{{ scope.row.created | formatDate('short') }}</span>
|
||||||
scope.row.created | formatDate('short')
|
|
||||||
}}</span>
|
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column :label="$t('table.moderation.type')" prop="type" width="100">
|
||||||
:label="$t('table.moderation.type')"
|
|
||||||
prop="type"
|
|
||||||
width="100"
|
|
||||||
>
|
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<span
|
<span v-text="$t('view.moderation.filters.' + scope.row.type)"></span>
|
||||||
v-text="$t('view.moderation.filters.' + scope.row.type)"
|
|
||||||
></span>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column :label="$t('table.moderation.source')" prop="sourceDisplayName">
|
||||||
:label="$t('table.moderation.source')"
|
|
||||||
prop="sourceDisplayName"
|
|
||||||
>
|
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<span
|
<span
|
||||||
class="x-link"
|
class="x-link"
|
||||||
@@ -87,10 +69,7 @@
|
|||||||
></span>
|
></span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column :label="$t('table.moderation.target')" prop="targetDisplayName">
|
||||||
:label="$t('table.moderation.target')"
|
|
||||||
prop="targetDisplayName"
|
|
||||||
>
|
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<span
|
<span
|
||||||
class="x-link"
|
class="x-link"
|
||||||
@@ -99,15 +78,9 @@
|
|||||||
></span>
|
></span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column :label="$t('table.moderation.action')" width="80" align="right">
|
||||||
:label="$t('table.moderation.action')"
|
|
||||||
width="80"
|
|
||||||
align="right"
|
|
||||||
>
|
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<template
|
<template v-if="scope.row.sourceUserId === API.currentUser.id">
|
||||||
v-if="scope.row.sourceUserId === Api.currentUser.id"
|
|
||||||
>
|
|
||||||
<el-button
|
<el-button
|
||||||
v-if="shiftHeld"
|
v-if="shiftHeld"
|
||||||
style="color: #f56c6c"
|
style="color: #f56c6c"
|
||||||
@@ -135,18 +108,15 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ModerationTab',
|
name: 'ModerationTab',
|
||||||
|
inject: ['API', 'showUserDialog'],
|
||||||
props: {
|
props: {
|
||||||
Api: Object,
|
|
||||||
tableData: Object,
|
tableData: Object,
|
||||||
showUserDialog: Function,
|
shiftHeld: Boolean,
|
||||||
shiftHeld: Boolean
|
hideTooltips: Boolean
|
||||||
},
|
},
|
||||||
created: async function () {
|
created: async function () {
|
||||||
this.filters[0].value = JSON.parse(
|
this.filters[0].value = JSON.parse(
|
||||||
await configRepository.getString(
|
await configRepository.getString('VRCX_playerModerationTableFilters', '[]')
|
||||||
'VRCX_playerModerationTableFilters',
|
|
||||||
'[]'
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -155,8 +125,7 @@
|
|||||||
{
|
{
|
||||||
prop: 'type',
|
prop: 'type',
|
||||||
value: [],
|
value: [],
|
||||||
filterFn: (row, filter) =>
|
filterFn: (row, filter) => filter.value.some((v) => v === row.type)
|
||||||
filter.value.some((v) => v === row.type)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: ['sourceDisplayName', 'targetDisplayName'],
|
prop: ['sourceDisplayName', 'targetDisplayName'],
|
||||||
@@ -191,32 +160,25 @@
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
saveTableFilters() {
|
saveTableFilters() {
|
||||||
configRepository.setString(
|
configRepository.setString('VRCX_playerModerationTableFilters', JSON.stringify(this.filters[0].value));
|
||||||
'VRCX_playerModerationTableFilters',
|
|
||||||
JSON.stringify(this.filters[0].value)
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
deletePlayerModeration(row) {
|
deletePlayerModeration(row) {
|
||||||
this.Api.deletePlayerModeration({
|
this.API.deletePlayerModeration({
|
||||||
moderated: row.targetUserId,
|
moderated: row.targetUserId,
|
||||||
type: row.type
|
type: row.type
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
deletePlayerModerationPrompt(row) {
|
deletePlayerModerationPrompt(row) {
|
||||||
this.$confirm(
|
this.$confirm(`Continue? Delete Moderation ${row.type}`, 'Confirm', {
|
||||||
`Continue? Delete Moderation ${row.type}`,
|
confirmButtonText: 'Confirm',
|
||||||
'Confirm',
|
cancelButtonText: 'Cancel',
|
||||||
{
|
type: 'info',
|
||||||
confirmButtonText: 'Confirm',
|
callback: (action) => {
|
||||||
cancelButtonText: 'Cancel',
|
if (action === 'confirm') {
|
||||||
type: 'info',
|
this.deletePlayerModeration(row);
|
||||||
callback: (action) => {
|
|
||||||
if (action === 'confirm') {
|
|
||||||
this.deletePlayerModeration(row);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+2
-1
@@ -13,7 +13,8 @@ module.exports = {
|
|||||||
'noty',
|
'noty',
|
||||||
'vue',
|
'vue',
|
||||||
'vue-data-tables',
|
'vue-data-tables',
|
||||||
'vue-lazyload'
|
'vue-lazyload',
|
||||||
|
'dayjs'
|
||||||
],
|
],
|
||||||
app: {
|
app: {
|
||||||
import: ['./src/app.js', './src/app.scss'],
|
import: ['./src/app.js', './src/app.scss'],
|
||||||
|
|||||||
Reference in New Issue
Block a user