diff --git a/.eslintrc.json b/.eslintrc.json index 9b748fc6..76959199 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,13 +1,14 @@ { "root": true, - "extends": ["eslint:all", "prettier"], + "extends": ["eslint:all", "plugin:vue/recommended", "prettier"], "env": { "browser": true, "commonjs": true, "es2021": true }, - "parser": "@babel/eslint-parser", + "parser": "vue-eslint-parser", "parserOptions": { + "parser": "@babel/eslint-parser", "ecmaVersion": "latest", "sourceType": "module", "ecmaFeatures": { diff --git a/package-lock.json b/package-lock.json index 599e5d43..224e88a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,12 +25,15 @@ "copy-webpack-plugin": "^12.0.2", "cross-env": "^7.0.3", "css-loader": "^7.1.2", + "dayjs": "^1.11.13", "default-passive-events": "^2.0.0", + "echarts": "^5.6.0", "electron": "^34.0.2", "electron-builder": "^25.1.8", "element-ui": "^2.15.14", "eslint": "^9.19.0", "eslint-config-prettier": "^10.0.1", + "eslint-plugin-vue": "^9.32.0", "html-webpack-plugin": "^5.6.3", "mini-css-extract-plugin": "^2.9.2", "normalize.css": "^8.0.1", @@ -1967,9 +1970,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz", - "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==", + "version": "9.20.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.20.0.tgz", + "integrity": "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==", "dev": true, "license": "MIT", "engines": { @@ -4811,6 +4814,13 @@ "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": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", @@ -5164,6 +5174,24 @@ "dev": true, "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": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -5439,18 +5467,18 @@ } }, "node_modules/eslint": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz", - "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==", + "version": "9.20.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz", + "integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.10.0", + "@eslint/core": "^0.11.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.19.0", + "@eslint/js": "9.20.0", "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -5511,6 +5539,85 @@ "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": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -5533,6 +5640,19 @@ "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": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", @@ -10331,6 +10451,102 @@ "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": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz", @@ -10751,6 +10967,16 @@ "dev": true, "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": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", @@ -10887,6 +11113,23 @@ "funding": { "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" } } } diff --git a/package.json b/package.json index ec87c563..4e49df07 100644 --- a/package.json +++ b/package.json @@ -41,12 +41,15 @@ "copy-webpack-plugin": "^12.0.2", "cross-env": "^7.0.3", "css-loader": "^7.1.2", + "dayjs": "^1.11.13", "default-passive-events": "^2.0.0", + "echarts": "^5.6.0", "electron": "^34.0.2", "electron-builder": "^25.1.8", "element-ui": "^2.15.14", "eslint": "^9.19.0", "eslint-config-prettier": "^10.0.1", + "eslint-plugin-vue": "^9.32.0", "html-webpack-plugin": "^5.6.3", "mini-css-extract-plugin": "^2.9.2", "normalize.css": "^8.0.1", diff --git a/src/app.js b/src/app.js index 8476239b..179105cd 100644 --- a/src/app.js +++ b/src/app.js @@ -16,6 +16,10 @@ import VueLazyload from 'vue-lazyload'; import VueI18n from 'vue-i18n'; import { DataTables } from 'vue-data-tables'; 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 'default-passive-events'; @@ -33,6 +37,7 @@ import _vrcxJsonStorage from './classes/vrcxJsonStorage.js'; // tabs import ModerationTab from './views/tabs/Moderation.vue'; +import ChartsTab from './views/tabs/Charts.vue'; // components import SimpleSwitch from './components/settings/SimpleSwitch.vue'; @@ -109,6 +114,13 @@ console.log(`isLinux: ${LINUX}`); }); // #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 let $app = {}; const API = new _apiInit($app); @@ -164,6 +176,7 @@ console.log(`isLinux: ${LINUX}`); components: { // tabs ModerationTab, + ChartsTab, // components // - settings @@ -173,6 +186,12 @@ console.log(`isLinux: ${LINUX}`); // - sidebar(friendsListSidebar) GroupsSidebar }, + provide() { + return { + API, + showUserDialog: this.showUserDialog + }; + }, el: '#x-app', async mounted() { await this.initLanguage(); @@ -4824,7 +4843,9 @@ console.log(`isLinux: ${LINUX}`); }); worldName = args.ref.name; } - } catch (err) {} + } catch (e) { + throw e; + } return worldName; }; diff --git a/src/classes/uiComponents.js b/src/classes/uiComponents.js index ddeb240f..c96608cb 100644 --- a/src/classes/uiComponents.js +++ b/src/classes/uiComponents.js @@ -89,7 +89,8 @@ export default class extends baseClass { link: { type: Boolean, default: true - } + }, + isOpenPreviousInstanceInfoDialog: Boolean }, data() { return { @@ -177,7 +178,14 @@ export default class extends baseClass { API.$emit('SHOW_WORLD_DIALOG_SHORTNAME', this.hint); 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() { diff --git a/src/classes/utils.js b/src/classes/utils.js index 991442c4..eddfae31 100644 --- a/src/classes/utils.js +++ b/src/classes/utils.js @@ -40,13 +40,13 @@ export default { return obj; }, - timeToText(sec) { - var n = Number(sec); + timeToText(sec, isNeedSeconds = false) { + let n = Number(sec); if (isNaN(n)) { return this.escapeTag(sec); } n = Math.floor(n / 1000); - var arr = []; + const arr = []; if (n < 0) { n = -n; } @@ -62,7 +62,7 @@ export default { arr.push(`${Math.floor(n / 60)}m`); n %= 60; } - if (arr.length === 0 && n < 60) { + if (isNeedSeconds || (arr.length === 0 && n < 60)) { arr.push(`${n}s`); } return arr.join(' '); diff --git a/src/components/charts/InstanceActivity.vue b/src/components/charts/InstanceActivity.vue new file mode 100644 index 00000000..ac294d0b --- /dev/null +++ b/src/components/charts/InstanceActivity.vue @@ -0,0 +1,421 @@ + + + + Instance Activity + + + + + + {{ totalOnlineTime }} + + + + + + + No data here, try another day + + + + ยท + + + + + + + + diff --git a/src/components/charts/InstanceActivityDetail.vue b/src/components/charts/InstanceActivityDetail.vue new file mode 100644 index 00000000..37f95f9d --- /dev/null +++ b/src/components/charts/InstanceActivityDetail.vue @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + diff --git a/src/index.pug b/src/index.pug index 1b185cc0..cd424a09 100644 --- a/src/index.pug +++ b/src/index.pug @@ -51,6 +51,7 @@ html +menuitem('moderation', "{{ $t('nav_tooltip.moderation') }}", 'el-icon-finished') +menuitem('notification', "{{ $t('nav_tooltip.notification') }}", 'el-icon-bell') +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('settings', "{{ $t('nav_tooltip.settings') }}", 'el-icon-s-tools') @@ -83,10 +84,9 @@ html //- moderation moderation-tab( v-if='$refs.menu?.activeIndex === "moderation"' - :Api='API' :table-data='playerModerationTable' - :show-user-dialog='showUserDialog' - :shift-held='shiftHeld') + :shift-held='shiftHeld' + :hide-tooltips='hideTooltips') //- notification include ./mixins/tabs/notifications.pug @@ -100,6 +100,14 @@ html include ./mixins/tabs/friendsList.pug +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 include ./mixins/tabs/settings.pug +settingsTab diff --git a/src/localization/en/en.json b/src/localization/en/en.json index cdbd4626..91c7cd77 100644 --- a/src/localization/en/en.json +++ b/src/localization/en/en.json @@ -11,6 +11,7 @@ "moderation": "Moderation", "notification": "Notification", "friend_list": "Friend List", + "charts": "Charts", "profile": "Profile", "settings": "Settings" }, diff --git a/src/mixins/dialogs/openSourceSoftwareNotice.pug b/src/mixins/dialogs/openSourceSoftwareNotice.pug index 6486618a..4da3c1b0 100644 --- a/src/mixins/dialogs/openSourceSoftwareNotice.pug +++ b/src/mixins/dialogs/openSourceSoftwareNotice.pug @@ -398,3 +398,36 @@ mixin openSourceSoftwareNotice 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 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. diff --git a/src/mixins/friendsListSidebar.pug b/src/mixins/friendsListSidebar.pug index cf93be29..4cd02cc1 100644 --- a/src/mixins/friendsListSidebar.pug +++ b/src/mixins/friendsListSidebar.pug @@ -1,5 +1,6 @@ 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') el-select( v-model='quickSearch' diff --git a/src/repository/database.js b/src/repository/database.js index 45777d6b..0d610bb6 100644 --- a/src/repository/database.js +++ b/src/repository/database.js @@ -2772,6 +2772,80 @@ class Database { ); 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} + */ + 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(); diff --git a/src/theme.dark.scss b/src/theme.dark.scss index 579945f4..2f9147cd 100644 --- a/src/theme.dark.scss +++ b/src/theme.dark.scss @@ -432,3 +432,10 @@ button { padding: 2px 2px; border-radius: 4px; } +.el-divider { + background-color: #606266; +} +.el-divider__text { + background: #222; + color: #ffffff; +} diff --git a/src/theme.darkvanilla.scss b/src/theme.darkvanilla.scss index 79b836d4..abe6715f 100644 --- a/src/theme.darkvanilla.scss +++ b/src/theme.darkvanilla.scss @@ -696,3 +696,11 @@ i[class='el-icon-star-off']:not(.el-menu-item div.el-tooltip i) { .el-color-picker__panel { background-color: var(--dv_bg-top); } +.el-divider { + background-color: #606266; +} +.el-divider__text { + background: var(--dv_bg-top); + color: #efefef; + padding-bottom: 2px; +} diff --git a/src/theme.darkvanillaold.scss b/src/theme.darkvanillaold.scss index ff9a8f25..92032e05 100644 --- a/src/theme.darkvanillaold.scss +++ b/src/theme.darkvanillaold.scss @@ -318,3 +318,10 @@ path[stroke='#e5e9f2'] { border: transparent; background-color: #333 !important; } +.el-divider { + background-color: #606266; +} +.el-divider__text { + background: var(--farback); + color: #efefef; +} diff --git a/src/theme.material3.scss b/src/theme.material3.scss index 3af080b1..abb52c9e 100644 --- a/src/theme.material3.scss +++ b/src/theme.material3.scss @@ -2012,3 +2012,10 @@ i.x-user-status { .el-dialog__body .el-tag--mini { line-height: 28px; } +.el-divider { + background-color: #606266; +} +.el-divider__text { + background: rgba(var(--md-sys-color-background)); + color: #efefef; +} diff --git a/src/theme.pink.scss b/src/theme.pink.scss index 40e6d8d1..2cdd3ebc 100644 --- a/src/theme.pink.scss +++ b/src/theme.pink.scss @@ -366,3 +366,10 @@ input[type='checkbox']:checked + .el-switch__core { border: transparent; background-color: #333; } +.el-divider { + background-color: #606266; +} +.el-divider__text { + background: var(--bg); + color: #efefef; +} diff --git a/src/views/tabs/Charts.vue b/src/views/tabs/Charts.vue new file mode 100644 index 00000000..21538f1e --- /dev/null +++ b/src/views/tabs/Charts.vue @@ -0,0 +1,29 @@ + + + + Charts + + + + + + + diff --git a/src/views/tabs/Moderation.vue b/src/views/tabs/Moderation.vue index 44674a91..ee9666a6 100644 --- a/src/views/tabs/Moderation.vue +++ b/src/views/tabs/Moderation.vue @@ -6,7 +6,7 @@ :filters="filters" :tableProps="tableProps" :paginationProps="paginationProps" - v-loading="Api.isPlayerModerationsLoading" + v-loading="API.isPlayerModerationsLoading" > @@ -37,48 +37,30 @@ > - + - {{ - scope.row.created | formatDate('long') - }} + {{ scope.row.created | formatDate('long') }} - {{ - scope.row.created | formatDate('short') - }} + {{ scope.row.created | formatDate('short') }} - + - + - + - + - + - + - filter.value.some((v) => v === row.type) + filterFn: (row, filter) => filter.value.some((v) => v === row.type) }, { prop: ['sourceDisplayName', 'targetDisplayName'], @@ -191,32 +160,25 @@ }, methods: { saveTableFilters() { - configRepository.setString( - 'VRCX_playerModerationTableFilters', - JSON.stringify(this.filters[0].value) - ); + configRepository.setString('VRCX_playerModerationTableFilters', JSON.stringify(this.filters[0].value)); }, deletePlayerModeration(row) { - this.Api.deletePlayerModeration({ + this.API.deletePlayerModeration({ moderated: row.targetUserId, type: row.type }); }, deletePlayerModerationPrompt(row) { - this.$confirm( - `Continue? Delete Moderation ${row.type}`, - 'Confirm', - { - confirmButtonText: 'Confirm', - cancelButtonText: 'Cancel', - type: 'info', - callback: (action) => { - if (action === 'confirm') { - this.deletePlayerModeration(row); - } + this.$confirm(`Continue? Delete Moderation ${row.type}`, 'Confirm', { + confirmButtonText: 'Confirm', + cancelButtonText: 'Cancel', + type: 'info', + callback: (action) => { + if (action === 'confirm') { + this.deletePlayerModeration(row); } } - ); + }); } } }; diff --git a/webpack.config.js b/webpack.config.js index 0da24aea..15e9ac12 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,7 +13,8 @@ module.exports = { 'noty', 'vue', 'vue-data-tables', - 'vue-lazyload' + 'vue-lazyload', + 'dayjs' ], app: { import: ['./src/app.js', './src/app.scss'],