diff --git a/html/package-lock.json b/html/package-lock.json index e67512f2..570f155b 100644 --- a/html/package-lock.json +++ b/html/package-lock.json @@ -12394,6 +12394,11 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "vuejs-toggle-switch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/vuejs-toggle-switch/-/vuejs-toggle-switch-1.3.2.tgz", + "integrity": "sha512-HkSB0hBWgol/o2LY9c1E/f4x+pE09uzlKAYs8bdmHexyudFO1/O1xFJA9M9aDe8DpIAXNxrZCwNwIZdCV97Sdw==" + }, "watchpack": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", diff --git a/html/package.json b/html/package.json index ce9c978b..6ae99df8 100644 --- a/html/package.json +++ b/html/package.json @@ -45,6 +45,7 @@ "stylelint-scss": "^3.18.0", "vue": "^2.6.12", "vue-data-tables": "^3.4.5", - "vue-lazyload": "^1.3.3" + "vue-lazyload": "^1.3.3", + "vuejs-toggle-switch": "^1.3.2" } } diff --git a/html/src/app.js b/html/src/app.js index 719d9dd9..d1b8e50d 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -8,6 +8,7 @@ import Noty from 'noty'; import Vue from 'vue'; import VueLazyload from 'vue-lazyload'; import { DataTables } from 'vue-data-tables'; +import ToggleSwitch from 'vuejs-toggle-switch' import ElementUI from 'element-ui'; import locale from 'element-ui/lib/locale/lang/en'; @@ -3415,7 +3416,6 @@ speechSynthesis.getVoices(); AppApi.CheckGameRunning().then(([isGameRunning, isGameNoVR]) => { if (isGameRunning !== this.isGameRunning) { this.isGameRunning = isGameRunning; - sharedRepository.setBool('is_game_running', isGameRunning); Discord.SetTimestamps(Date.now(), 0); } this.isGameNoVR = isGameNoVR; @@ -3472,9 +3472,7 @@ speechSynthesis.getVoices(); } var ctx = data[--i]; // GPS, Online, Offline, Status, Avatar - if ((ctx.type !== 'Avatar') && - !((ctx.type === 'GPS') && (ctx.location[0] === 'private') && (this.hidePrivateFromFeed === true)) && - !(((ctx.type === 'Online') || (ctx.type === 'Offline')) && (this.hideLoginsFromFeed === true))) { + if (ctx.type !== 'Avatar') { arr.push({ ...ctx, isFriend: this.friends.has(ctx.userId), @@ -3483,6 +3481,7 @@ speechSynthesis.getVoices(); ++j; } } + // invite, requestInvite, friendRequest var { data } = this.notificationTable; for (i = 0; i < data.length; i++) { var ctx = data[i]; @@ -5705,7 +5704,6 @@ speechSynthesis.getVoices(); $app.data.openVRAlways = configRepository.getBool('openVRAlways'); $app.data.overlaybutton = configRepository.getBool('VRCX_overlaybutton'); $app.data.hidePrivateFromFeed = configRepository.getBool('VRCX_hidePrivateFromFeed'); - $app.data.hideLoginsFromFeed = configRepository.getBool('VRCX_hideLoginsFromFeed'); $app.data.hideDevicesFromFeed = configRepository.getBool('VRCX_hideDevicesFromFeed'); $app.data.overlayNotifications = configRepository.getBool('VRCX_overlayNotifications'); $app.data.minimalFeed = configRepository.getBool('VRCX_minimalFeed'); @@ -5717,10 +5715,10 @@ speechSynthesis.getVoices(); configRepository.setBool('openVRAlways', this.openVRAlways); configRepository.setBool('VRCX_overlaybutton', this.overlaybutton); configRepository.setBool('VRCX_hidePrivateFromFeed', this.hidePrivateFromFeed); - configRepository.setBool('VRCX_hideLoginsFromFeed', this.hideLoginsFromFeed); configRepository.setBool('VRCX_hideDevicesFromFeed', this.hideDevicesFromFeed); configRepository.setBool('VRCX_overlayNotifications', this.overlayNotifications); configRepository.setBool('VRCX_minimalFeed', this.minimalFeed); + AppApi.RefreshVR(); }; $app.data.TTSvoices = speechSynthesis.getVoices(); var saveNotificationTTS = function () { @@ -5729,12 +5727,12 @@ speechSynthesis.getVoices(); if (this.notificationTTS) { this.speak('Notification text-to-speech enabled'); } + AppApi.RefreshVR(); }; $app.watch.openVR = saveOpenVROption; $app.watch.openVRAlways = saveOpenVROption; $app.watch.overlaybutton = saveOpenVROption; $app.watch.hidePrivateFromFeed = saveOpenVROption; - $app.watch.hideLoginsFromFeed = saveOpenVROption; $app.watch.hideDevicesFromFeed = saveOpenVROption; $app.watch.overlayNotifications = saveOpenVROption; $app.watch.minimalFeed = saveOpenVROption; @@ -5744,6 +5742,7 @@ speechSynthesis.getVoices(); $app.watch.isDarkMode = function () { configRepository.setBool('isDarkMode', this.isDarkMode); $appDarkStyle.disabled = this.isDarkMode === false; + AppApi.RefreshVR(); }; $app.data.isStartAtWindowsStartup = configRepository.getBool('VRCX_StartAtWindowsStartup'); $app.data.isStartAsMinimizedState = (VRCXStorage.Get('VRCX_StartAsMinimizedState') === 'true'); @@ -5760,40 +5759,142 @@ speechSynthesis.getVoices(); $app.watch.isStartAsMinimizedState = saveVRCXWindowOption; $app.watch.isCloseToTray = saveVRCXWindowOption; $app.watch.isAutoLogin = saveVRCXWindowOption; - if (!configRepository.getString('VRCX_notificationTimeout')) { - $app.data.notificationTimeout = 3000; - configRepository.setString('VRCX_notificationTimeout', $app.data.notificationTimeout); - } - if (!configRepository.getString('VRCX_notificationJoinLeaveFilter')) { - $app.data.notificationJoinLeaveFilter = 'VIP'; - configRepository.setString('VRCX_notificationJoinLeaveFilter', $app.data.notificationJoinLeaveFilter); - } - if (!configRepository.getString('VRCX_notificationOnlineOfflineFilter')) { - $app.data.notificationOnlineOfflineFilter = 'VIP'; - configRepository.setString('VRCX_notificationOnlineOfflineFilter', $app.data.notificationOnlineOfflineFilter); - } + + //setting defaults if (!configRepository.getString('VRCX_notificationPosition')) { $app.data.notificationPosition = 'topCenter'; configRepository.setString('VRCX_notificationPosition', $app.data.notificationPosition); } + if (!configRepository.getString('VRCX_notificationTimeout')) { + $app.data.notificationTimeout = 3000; + configRepository.setString('VRCX_notificationTimeout', $app.data.notificationTimeout); + } if (!configRepository.getString('VRCX_notificationTTSVoice')) { $app.data.notificationTTSVoice = '0'; configRepository.setString('VRCX_notificationTTSVoice', $app.data.notificationTTSVoice); } - $app.data.notificationJoinLeaveFilter = configRepository.getString('VRCX_notificationJoinLeaveFilter'); - $app.methods.changeNotificationJoinLeaveFilter = function () { - configRepository.setString('VRCX_notificationJoinLeaveFilter', this.notificationJoinLeaveFilter); + if (!configRepository.getString('sharedFeedFilters')) { + var sharedFeedFilters = { + noty: {}, + wrist: {} + }; + sharedFeedFilters.noty.Location = 'Off'; + sharedFeedFilters.noty.OnPlayerJoined = 'VIP'; + sharedFeedFilters.noty.OnPlayerLeft = 'VIP'; + sharedFeedFilters.noty.OnPlayerJoining = 'VIP'; + sharedFeedFilters.noty.Online = 'VIP'; + sharedFeedFilters.noty.Offline = 'VIP'; + sharedFeedFilters.noty.GPS = 'Off'; + sharedFeedFilters.noty.Status = 'Off'; + sharedFeedFilters.noty.invite = 'Friends'; + sharedFeedFilters.noty.requestInvite = 'Friends'; + sharedFeedFilters.noty.friendRequest = 'On'; + sharedFeedFilters.noty.Friend = 'On'; + sharedFeedFilters.noty.Unfriend = 'On'; + sharedFeedFilters.noty.DisplayName = 'VIP'; + sharedFeedFilters.noty.TrustLevel = 'VIP'; + sharedFeedFilters.wrist.Location = 'On'; + sharedFeedFilters.wrist.OnPlayerJoined = 'Everyone'; + sharedFeedFilters.wrist.OnPlayerLeft = 'Everyone'; + sharedFeedFilters.wrist.OnPlayerJoining = 'Friends'; + sharedFeedFilters.wrist.Online = 'Friends'; + sharedFeedFilters.wrist.Offline = 'Friends'; + sharedFeedFilters.wrist.GPS = 'Friends'; + sharedFeedFilters.wrist.Status = 'Friends'; + sharedFeedFilters.wrist.invite = 'Friends'; + sharedFeedFilters.wrist.requestInvite = 'Friends'; + sharedFeedFilters.wrist.friendRequest = 'On'; + sharedFeedFilters.wrist.Friend = 'On'; + sharedFeedFilters.wrist.Unfriend = 'On'; + sharedFeedFilters.wrist.DisplayName = 'Friends'; + sharedFeedFilters.wrist.TrustLevel = 'Friends'; + configRepository.setString('sharedFeedFilters', JSON.stringify(sharedFeedFilters)); + } + $app.data.sharedFeedFilters = JSON.parse(configRepository.getString('sharedFeedFilters')); + + $app.data.toggleSwitchOptionsEveryone = { + layout: { + backgroundColor: "white", + selectedBackgroundColor: "#409eff", + selectedColor: "white", + color: "#409eff", + borderColor: "#409eff", + fontWeight: "bold", + fontFamily: '"Noto Sans JP", "Noto Sans KR", "Meiryo UI", "Malgun Gothic", "Segoe UI", "sans-serif"' + }, + size: { + height: 1.5, + width: 15, + padding: 0.1, + fontSize: 0.75 + }, + items: { + labels: [{ name: "Off" }, { name: "VIP" }, { name: "Friends" }, { name: "Everyone" }] + } }; - $app.data.notificationOnlineOfflineFilter = configRepository.getString('VRCX_notificationOnlineOfflineFilter'); - $app.methods.changeNotificationOnlineOfflineFilter = function () { - configRepository.setString('VRCX_notificationOnlineOfflineFilter', this.notificationOnlineOfflineFilter); + $app.data.toggleSwitchOptionsFriends = { + layout: { + backgroundColor: "white", + selectedBackgroundColor: "#409eff", + selectedColor: "white", + color: "#409eff", + borderColor: "#409eff", + fontWeight: "bold", + fontFamily: '"Noto Sans JP", "Noto Sans KR", "Meiryo UI", "Malgun Gothic", "Segoe UI", "sans-serif"' + }, + size: { + height: 1.5, + width: 11.25, + padding: 0.1, + fontSize: 0.75 + }, + items: { + labels: [{ name: "Off" }, { name: "VIP" }, { name: "Friends" }] + } }; + $app.data.toggleSwitchOptionsOn = { + layout: { + backgroundColor: "white", + selectedBackgroundColor: "#409eff", + selectedColor: "white", + color: "#409eff", + borderColor: "#409eff", + fontWeight: "bold", + fontFamily: '"Noto Sans JP", "Noto Sans KR", "Meiryo UI", "Malgun Gothic", "Segoe UI", "sans-serif"' + }, + size: { + height: 1.5, + width: 7.5, + padding: 0.1, + fontSize: 0.75 + }, + items: { + labels: [{ name: "Off" }, { name: "On" }] + } + }; + + $app.methods.saveSharedFeedFilters = function () { + this.notyFeedFiltersDialog.visible = false; + this.wristFeedFiltersDialog.visible = false; + configRepository.setString('sharedFeedFilters', JSON.stringify(this.sharedFeedFilters)); + AppApi.RefreshVR(); + } + + $app.methods.cancelSharedFeedFilters = function () { + this.notyFeedFiltersDialog.visible = false; + this.wristFeedFiltersDialog.visible = false; + this.sharedFeedFilters = JSON.parse(configRepository.getString('sharedFeedFilters')); + } + $app.data.notificationPosition = configRepository.getString('VRCX_notificationPosition'); $app.methods.changeNotificationPosition = function () { configRepository.setString('VRCX_notificationPosition', this.notificationPosition); + AppApi.RefreshVR(); }; + sharedRepository.setBool('is_game_running', false); var isGameRunningStateChange = function () { + sharedRepository.setBool('is_game_running', this.isGameRunning); $app.lastLocation = ''; if (this.isGameRunning) { API.currentUser.$online_for = Date.now(); @@ -5872,6 +5973,7 @@ speechSynthesis.getVoices(); var voiceName = voices[index].name; speechSynthesis.cancel(); this.speak(voiceName); + AppApi.RefreshVR(); }; $app.methods.speak = function (text) { @@ -7579,6 +7681,28 @@ speechSynthesis.getVoices(); this.notificationPositionDialog.visible = true; }; + // App: Noty feed filters + + $app.data.notyFeedFiltersDialog = { + visible: false + }; + + $app.methods.showNotyFeedFiltersDialog = function () { + this.$nextTick(() => adjustDialogZ(this.$refs.notyFeedFiltersDialog.$el)); + this.notyFeedFiltersDialog.visible = true; + }; + + // App: Wrist feed filters + + $app.data.wristFeedFiltersDialog = { + visible: false + }; + + $app.methods.showWristFeedFiltersDialog = function () { + this.$nextTick(() => adjustDialogZ(this.$refs.wristFeedFiltersDialog.$el)); + this.wristFeedFiltersDialog.visible = true; + }; + // App: Launch Dialog $app.data.launchDialog = { diff --git a/html/src/app.scss b/html/src/app.scss index 06d92a44..8cf13ef4 100644 --- a/html/src/app.scss +++ b/html/src/app.scss @@ -567,3 +567,19 @@ i.x-user-status.busy { display: inline-block; min-width: 175px; } + +.toggle-switch { + display: inline-block; +} + +.toggle-list { + font-size: 12px; +} + +.toggle-list .toggle-name { + display: inline-block; + min-width: 100px; + padding: 2px 5px 2px 0; + vertical-align: top; + text-align: right; +} diff --git a/html/src/index.pug b/html/src/index.pug index 4d3215df..37f6d765 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -515,18 +515,18 @@ html span.name VRCPlus Profile Icons el-switch(v-model="displayVRCPlusIconsAsAvatar") div.options-container - span.header Friends Sort Option + span.header Side Pannel Sorting Options div.options-container-item span.name VIP el-switch(v-model="orderFriendsGroup0" inactive-text="by name" active-text="by state") div.options-container-item - span.name ONLINE + span.name Online el-switch(v-model="orderFriendsGroup1" inactive-text="by name" active-text="by state") div.options-container-item - span.name ACTIVE + span.name Active el-switch(v-model="orderFriendsGroup2" inactive-text="by name" active-text="by state") div.options-container-item - span.name OFFLINE + span.name Offline el-switch(v-model="orderFriendsGroup3" inactive-text="by name" active-text="by state") div.options-container span.header Discord Presence @@ -560,9 +560,6 @@ html div.options-container-item span.name Hide VR Devices el-switch(v-model="hideDevicesFromFeed" :disabled="!openVR") - div.options-container-item - span.name Hide Online/Offline - el-switch(v-model="hideLoginsFromFeed" :disabled="!openVR") div.options-container-item span.name Hide Private Worlds el-switch(v-model="hidePrivateFromFeed" :disabled="!openVR") @@ -581,24 +578,12 @@ html el-button(v-text="TTSvoices[notificationTTSVoice].name" size="mini" :disabled="!openVR || !notificationTTS") el-dropdown-menu(#default="dropdown") el-dropdown-item(v-if="voice" v-for="(voice, index) in TTSvoices" :key="index" v-text="voice.name" :command="index") + div.options-container-item + el-button(size="small" icon="el-icon-notebook-2" @click="showWristFeedFiltersDialog()" :disabled="!openVR") Wrist Feed Filters + el-button(size="small" icon="el-icon-chat-square" @click="showNotyFeedFiltersDialog()" :disabled="!overlayNotifications || !openVR") Notification Filters div.options-container-item el-button(size="small" icon="el-icon-time" @click="promptNotificationTimeout()" :disabled="!overlayNotifications || !openVR") Notification Timeout el-button(size="small" icon="el-icon-rank" @click="showNotificationPositionDialog()" :disabled="!overlayNotifications || !openVR") Notification Position - div.options-container-item - span Join/Leave Notifications - br - el-radio-group(v-model="notificationJoinLeaveFilter" size="mini" @change="changeNotificationJoinLeaveFilter" :disabled="!overlayNotifications && !notificationTTS || !openVR") - el-radio(label="VIP" v-model="notificationJoinLeaveFilter") VIP - el-radio(label="Friends" v-model="notificationJoinLeaveFilter") Friends - el-radio(label="Everyone" v-model="notificationJoinLeaveFilter") Everyone - el-radio(label="Off" v-model="notificationJoinLeaveFilter") Off - div.options-container-item - span Online/Offline Notifications - br - el-radio-group(v-model="notificationOnlineOfflineFilter" size="mini" @change="changeNotificationOnlineOfflineFilter" :disabled="!overlayNotifications && !notificationTTS || !openVR") - el-radio(label="VIP" v-model="notificationOnlineOfflineFilter") VIP - el-radio(label="Friends" v-model="notificationOnlineOfflineFilter") Friends - el-radio(label="Off" v-model="notificationOnlineOfflineFilter") Off div.options-container span.header Application div.options-container-item @@ -1206,6 +1191,107 @@ html div(style="display:flex") el-button(type="primary" size="small" style="margin-left:auto" @click="notificationPositionDialog.visible = false") OK + //- dialog: Noty feed filters + el-dialog.x-dialog(ref="notyFeedFiltersDialog" :visible.sync="notyFeedFiltersDialog.visible" title="Notification Filters" width="400px") + div.toggle-list + div + span.toggle-name OnPlayerJoining + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchNotyGroupOnPlayerJoining" v-model="sharedFeedFilters.noty.OnPlayerJoining" class="toggle-switch") + div + span.toggle-name OnPlayerJoined + toggle-switch(:options="toggleSwitchOptionsEveryone" group="switchNotyGroupOnPlayerJoined" v-model="sharedFeedFilters.noty.OnPlayerJoined" class="toggle-switch") + div + span.toggle-name OnPlayerLeft + toggle-switch(:options="toggleSwitchOptionsEveryone" group="switchNotyGroupOnPlayerLeft" v-model="sharedFeedFilters.noty.OnPlayerLeft" class="toggle-switch") + div + span.toggle-name Online + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchNotyGroupOnline" v-model="sharedFeedFilters.noty.Online" class="toggle-switch") + div + span.toggle-name Offline + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchNotyGroupOffline" v-model="sharedFeedFilters.noty.Offline" class="toggle-switch") + div + span.toggle-name GPS + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchNotyGroupGPS" v-model="sharedFeedFilters.noty.GPS" class="toggle-switch") + div + span.toggle-name Status + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchNotyGroupStatus" v-model="sharedFeedFilters.noty.Status" class="toggle-switch") + div + span.toggle-name Invite + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchNotyGroupinvite" v-model="sharedFeedFilters.noty.invite" class="toggle-switch") + div + span.toggle-name Request Invite + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchNotyGrouprequestInvite" v-model="sharedFeedFilters.noty.requestInvite" class="toggle-switch") + div + span.toggle-name Friend Request + toggle-switch(:options="toggleSwitchOptionsOn" group="switchNotyGrouprequestfriendRequest" v-model="sharedFeedFilters.noty.friendRequest" class="toggle-switch") + div + span.toggle-name New Friend + toggle-switch(:options="toggleSwitchOptionsOn" group="switchNotyGrouprequestFriend" v-model="sharedFeedFilters.noty.Friend" class="toggle-switch") + div + span.toggle-name Unfriend + toggle-switch(:options="toggleSwitchOptionsOn" group="switchNotyGrouprequestUnfriend" v-model="sharedFeedFilters.noty.Unfriend" class="toggle-switch") + div + span.toggle-name DisplayName + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchNotyGrouprequestDisplayName" v-model="sharedFeedFilters.noty.DisplayName" class="toggle-switch") + div + span.toggle-name TrustLevel + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchNotyGrouprequestTrustLevel" v-model="sharedFeedFilters.noty.TrustLevel" class="toggle-switch") + template(#footer) + el-button(type="small" @click="cancelSharedFeedFilters") Cancel + el-button(type="primary" size="small" style="margin-left:10px" @click="saveSharedFeedFilters") Save + + //- dialog: wrist feed filters + el-dialog.x-dialog(ref="wristFeedFiltersDialog" :visible.sync="wristFeedFiltersDialog.visible" title="Wrist Feed Filters" width="400px") + div.toggle-list + div + span.toggle-name Self Location + toggle-switch(:options="toggleSwitchOptionsOn" group="switchWristGroupLocation" v-model="sharedFeedFilters.wrist.Location" class="toggle-switch") + div + span.toggle-name OnPlayerJoining + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchWristGroupOnPlayerJoining" v-model="sharedFeedFilters.wrist.OnPlayerJoining" class="toggle-switch") + div + span.toggle-name OnPlayerJoined + toggle-switch(:options="toggleSwitchOptionsEveryone" group="switchWristGroupOnPlayerJoined" v-model="sharedFeedFilters.wrist.OnPlayerJoined" class="toggle-switch") + div + span.toggle-name OnPlayerLeft + toggle-switch(:options="toggleSwitchOptionsEveryone" group="switchWristGroupOnPlayerLeft" v-model="sharedFeedFilters.wrist.OnPlayerLeft" class="toggle-switch") + div + span.toggle-name Online + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchWristGroupOnline" v-model="sharedFeedFilters.wrist.Online" class="toggle-switch") + div + span.toggle-name Offline + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchWristGroupOffline" v-model="sharedFeedFilters.wrist.Offline" class="toggle-switch") + div + span.toggle-name GPS + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchWristGroupGPS" v-model="sharedFeedFilters.wrist.GPS" class="toggle-switch") + div + span.toggle-name Status + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchWristGroupStatus" v-model="sharedFeedFilters.wrist.Status" class="toggle-switch") + div + span.toggle-name Invite + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchWristGroupinvite" v-model="sharedFeedFilters.wrist.invite" class="toggle-switch") + div + span.toggle-name Request Invite + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchWristGrouprequestInvite" v-model="sharedFeedFilters.wrist.requestInvite" class="toggle-switch") + div + span.toggle-name Friend Request + toggle-switch(:options="toggleSwitchOptionsOn" group="switchWristGrouprequestfriendRequest" v-model="sharedFeedFilters.wrist.friendRequest" class="toggle-switch") + div + span.toggle-name New Friend + toggle-switch(:options="toggleSwitchOptionsOn" group="switchWristGrouprequestFriend" v-model="sharedFeedFilters.wrist.Friend" class="toggle-switch") + div + span.toggle-name Unfriend + toggle-switch(:options="toggleSwitchOptionsOn" group="switchWristGrouprequestUnfriend" v-model="sharedFeedFilters.wrist.Unfriend" class="toggle-switch") + div + span.toggle-name DisplayName + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchWristGrouprequestDisplayName" v-model="sharedFeedFilters.wrist.DisplayName" class="toggle-switch") + div + span.toggle-name TrustLevel + toggle-switch(:options="toggleSwitchOptionsFriends" group="switchWristGrouprequestTrustLevel" v-model="sharedFeedFilters.wrist.TrustLevel" class="toggle-switch") + template(#footer) + el-button(type="small" @click="cancelSharedFeedFilters") Cancel + el-button(type="primary" size="small" @click="saveSharedFeedFilters") Save + //- dialog: open source software notice el-dialog.x-dialog(:visible.sync="ossDialog" title="Open Source Software Notice" width="650px") div(style="height:350px;overflow:hidden scroll;word-break:break-all") @@ -1480,6 +1566,30 @@ html 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. + div(style="margin-top:15px") + p(style="font-weight:bold") vuejs-toggle-switch + pre(style="font-size:12px;white-space:pre-line"). + MIT License + + Copyright (c) 2018 Lars-Martin Hejll + + 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 diff --git a/html/src/vr.js b/html/src/vr.js index f43e6918..16b8d962 100644 --- a/html/src/vr.js +++ b/html/src/vr.js @@ -573,9 +573,21 @@ speechSynthesis.getVoices(); currentTime: new Date().toJSON(), currentUserStatus: null, cpuUsage: 0, - feeds: [], + isGameRunning: false, + lastLocation: '', + lastFeedEntry: [], + feedFilters: [], + wristFeed: [], + notyMap: [], devices: [], + overlayNotificationsToggle: false, + notificationTTSToggle: false, + notificationTTSVoice: '0', + hideDevicesToggle: false, isMinimalFeed: false, + notificationPosition: 'topCenter', + notificationTimeout: '3000', + notificationTheme: 'relax' }, computed: {}, methods: {}, @@ -600,6 +612,8 @@ speechSynthesis.getVoices(); // FIXME: 어케 복구하냐 이건 throw err; }).then((args) => { + this.initConfigVars(); + this.initNotyMap(); this.updateLoop(); this.updateCpuUsageLoop(); this.$nextTick(function () { @@ -612,23 +626,70 @@ speechSynthesis.getVoices(); } }; + $app.methods.initConfigVars = function () { + this.notificationTTSToggle = configRepository.getBool('VRCX_notificationTTS'); + this.notificationTTSVoice = configRepository.getString('VRCX_notificationTTSVoice'); + this.overlayNotificationsToggle = configRepository.getBool('VRCX_overlayNotifications'); + this.hidePrivateFromFeed = configRepository.getBool('VRCX_hidePrivateFromFeed'); + this.hideDevicesToggle = configRepository.getBool('VRCX_hideDevicesFromFeed'); + this.isMinimalFeed = configRepository.getBool('VRCX_minimalFeed'); + this.feedFilters = JSON.parse(configRepository.getString('sharedFeedFilters')); + this.notificationPosition = configRepository.getString('VRCX_notificationPosition'); + this.notificationTimeout = configRepository.getString('VRCX_notificationTimeout'); + if (configRepository.getBool('isDarkMode')) { + this.notificationTheme = 'sunset'; + } else { + this.notificationTheme = 'relax'; + } + }; + + $app.methods.initNotyMap = function () { + var feeds = sharedRepository.getArray('feeds'); + if (feeds === null) { + return; + } + var filter = this.feedFilters.noty; + var filtered = []; + feeds.forEach((feed) => { + if (filter[feed.type]) { + if ((filter[feed.type] !== 'Off') && + ((filter[feed.type] === 'Everyone') || (filter[feed.type] === 'On') || + ((filter[feed.type] === 'Friends') && (feed.isFriend)) || + ((filter[feed.type] === 'VIP') && (feed.isFavorite)))) { + var displayName = ''; + if (feed.data) { + displayName = feed.data; + } else if (feed.displayName) { + displayName = feed.displayName; + } else if (feed.senderUsername) { + displayName = feed.senderUsername; + } + if ((displayName) && (!this.notyMap[displayName]) || + (this.notyMap[displayName] < feed.created_at)) { + this.notyMap[displayName] = feed.created_at; + } + } + } + }); + }; + $app.methods.updateLoop = async function () { try { this.currentTime = new Date().toJSON(); this.currentUserStatus = sharedRepository.getString('current_user_status'); this.isGameRunning = sharedRepository.getBool('is_game_running'); - if (configRepository.getBool('VRCX_hideDevicesFromFeed') === false) { + this.lastLocation = sharedRepository.getString('last_location'); + if (!this.hideDevicesToggle) { AppApi.GetVRDevices().then((devices) => { devices.forEach((device) => { device[2] = parseInt(device[2], 10); }); this.devices = devices; }); - } - else { + } else { this.devices = ''; } - await this.updateSharedFeed(); + await this.updateSharedFeeds(); } catch (err) { console.error(err); } @@ -645,185 +706,228 @@ speechSynthesis.getVoices(); setTimeout(() => this.updateCpuUsageLoop(), 1000); }; - $app.methods.updateSharedFeed = async function () { - // TODO: block mute hideAvatar unfriend - this.isMinimalFeed = configRepository.getBool('VRCX_minimalFeed'); - var notificationPosition = configRepository.getString('VRCX_notificationPosition'); - var notificationTimeout = configRepository.getString('VRCX_notificationTimeout'); - var notificationJoinLeaveFilter = configRepository.getString('VRCX_notificationJoinLeaveFilter'); - var notificationOnlineOfflineFilter = configRepository.getString('VRCX_notificationOnlineOfflineFilter'); - var theme = 'relax'; - if (configRepository.getBool('isDarkMode') === true) { - theme = 'sunset'; - } - + $app.methods.updateSharedFeeds = async function () { var feeds = sharedRepository.getArray('feeds'); if (feeds === null) { return; } - - var _feeds = this.feeds; - this.feeds = feeds; - - if ((this.appType === '2') && this.isGameRunning) { - var map = {}; - _feeds.forEach((feed) => { - if (feed.type === 'OnPlayerJoined' || - feed.type === 'OnPlayerLeft') { - if (!map[feed.data] || - map[feed.data] < feed.created_at) { - map[feed.data] = feed.created_at; - } - } else if (feed.type === 'Online' || - feed.type === 'Offline') { - if (!map[feed.displayName] || - map[feed.displayName] < feed.created_at) { - map[feed.displayName] = feed.created_at; - } - } else if (feed.type === 'invite' || - feed.type === 'requestInvite' || - feed.type === 'friendRequest') { - if (!map[feed.senderUsername] || - map[feed.senderUsername] < feed.created_at) { - map[feed.senderUsername] = feed.created_at; - } - } else if (feed.type === 'Friend' || - feed.type === 'Unfriend') { - if (!map[feed.displayName] || - map[feed.displayName] < feed.created_at) { - map[feed.displayName] = feed.created_at; - } - } - }); - // disable notification on busy - if (this.currentUserStatus === 'busy') { - return; - } - var notys = []; - this.feeds.forEach((feed) => { - if (((notificationOnlineOfflineFilter === "Friends") && (feed.isFriend)) || - ((notificationOnlineOfflineFilter === "VIP") && (feed.isFavorite))) { - if (feed.type === 'Online' || - feed.type === 'Offline') { - if (!map[feed.displayName] || - map[feed.displayName] < feed.created_at) { - map[feed.displayName] = feed.created_at; - notys.push(feed); - } - } - } - if ((notificationJoinLeaveFilter === "Everyone") || - ((notificationJoinLeaveFilter === "Friends") && (feed.isFriend)) || - ((notificationJoinLeaveFilter === "VIP") && (feed.isFavorite))) { - if (feed.type === 'OnPlayerJoined' || - feed.type === 'OnPlayerLeft') { - if (!map[feed.data] || - map[feed.data] < feed.created_at) { - map[feed.data] = feed.created_at; - notys.push(feed); - } - } - } - if (feed.type === 'invite' || - feed.type === 'requestInvite' || - feed.type === 'friendRequest') { - if (!map[feed.senderUsername] || - map[feed.senderUsername] < feed.created_at) { - map[feed.senderUsername] = feed.created_at; - notys.push(feed); - } - } - if (feed.type === 'Friend' || - feed.type === 'Unfriend') { - if (!map[feed.displayName] || - map[feed.displayName] < feed.created_at) { - map[feed.displayName] = feed.created_at; - notys.push(feed); - } - } - }); - var bias = new Date(Date.now() - 60000).toJSON(); - var theme = 'relax'; - if (configRepository.getBool('isDarkMode') === true) { - theme = 'sunset'; - } - notys.forEach((noty) => { - if (noty.created_at > bias) { - if (configRepository.getBool('VRCX_overlayNotifications')) { - var text = ''; - switch (noty.type) { - case 'OnPlayerJoined': - text = `${noty.data} has joined`; - break; - case 'OnPlayerLeft': - text = `${noty.data} has left`; - break; - case 'Online': - text = `${noty.displayName} has logged in`; - break; - case 'Offline': - text = `${noty.displayName} has logged out`; - break; - case 'invite': - text = `${noty.senderUsername} has invited you to ${noty.details.worldName}`; - break; - case 'requestInvite': - text = `${noty.senderUsername} has requested an invite`; - break; - case 'friendRequest': - text = `${noty.senderUsername} has sent you a friend request`; - break; - case 'Friend': - text = `${noty.displayName} is now your friend`; - break; - case 'Unfriend': - text = `${noty.displayName} has unfriended you`; - break; - } - if (text) { - new Noty({ - type: 'alert', - theme: theme, - timeout: notificationTimeout, - layout: notificationPosition, - text: text - }).show(); - } - } - if (configRepository.getBool('VRCX_notificationTTS')) { - switch (noty.type) { - case 'OnPlayerJoined': - this.speak(`${noty.data} has joined`); - break; - case 'OnPlayerLeft': - this.speak(`${noty.data} has left`); - break; - case 'Online': - this.speak(`${noty.displayName} has logged in`); - break; - case 'Offline': - this.speak(`${noty.displayName} has logged out`); - break; - case 'invite': - this.speak(`${noty.senderUsername} has invited you to ${noty.details.worldName}`); - break; - case 'requestInvite': - this.speak(`${noty.senderUsername} has requested an invite`); - break; - case 'friendRequest': - this.speak(`${noty.senderUsername} has sent you a friend request`); - break; - case 'Friend': - this.speak(`${noty.displayName} is now your friend`); - break; - case 'Unfriend': - this.speak(`${noty.displayName} has unfriended you`); - break; - } - } - } - }); + if ((this.lastFeedEntry !== undefined) && + (feeds[0].created_at === this.lastFeedEntry.created_at)) { + return; } + this.lastFeedEntry = feeds[0]; + + // OnPlayerJoining + var bias = new Date(Date.now() - 120000).toJSON(); + for (i = 0; i < feeds.length; i++) { + var ctx = feeds[i]; + if ((ctx.created_at < bias) || (ctx.type === 'Location')) { + break; + } + if ((ctx.type === 'GPS') && (ctx.location[0] === this.lastLocation)) { + var joining = true; + for (var k = 0; k < feeds.length; k++) { + var feedItem = feeds[k]; + if ((feedItem.type === 'OnPlayerJoined') && (feedItem.data === ctx.displayName)) { + joining = false; + break; + } + if ((feedItem.created_at < bias) || (feedItem.type === 'Location') || + ((feedItem.type === 'GPS') && (feedItem.location !== ctx.location[0]) && + (feedItem.displayName === ctx.displayName))) { + break; + } + } + if (joining) { + var onPlayerJoining = {}; + onPlayerJoining.created_at = ctx.created_at; + onPlayerJoining.data = ctx.displayName; + onPlayerJoining.isFavorite = ctx.isFavorite; + onPlayerJoining.isFriend = ctx.isFriend; + onPlayerJoining.type = 'OnPlayerJoining'; + feeds.splice(i, 0, onPlayerJoining); + i++; + } + } + } + + if (this.hidePrivateFromFeed) { + for (var i = 0; i < feeds.length; i++) { + var feed = feeds[i]; + if ((feed.type === 'GPS') && (feed.location[0] === 'private')) { + feeds.splice(i, 1); + i--; + } + } + } + + if (this.appType === '1') { + this.updateSharedFeedWrist(feeds); + } + if (this.appType === '2') { + this.updateSharedFeedNoty(feeds); + } + }; + + $app.methods.updateSharedFeedWrist = async function (feeds) { + var filter = this.feedFilters.wrist; + var filtered = []; + feeds.forEach((feed) => { + if (filter[feed.type]) { + if ((filter[feed.type] !== 'Off') && + ((filter[feed.type] === 'Everyone') || (filter[feed.type] === 'On') || + ((filter[feed.type] === 'Friends') && (feed.isFriend)) || + ((filter[feed.type] === 'VIP') && (feed.isFavorite)))) { + filtered.push(feed); + } + } else { + console.error(`missing feed filter for ${feed.type}`); + filtered.push(feed); + } + }); + this.wristFeed = filtered; + }; + + $app.methods.updateSharedFeedNoty = async function (feeds) { + var filter = this.feedFilters.noty; + var filtered = []; + feeds.forEach((feed) => { + if (filter[feed.type]) { + if ((filter[feed.type] !== 'Off') && + ((filter[feed.type] === 'Everyone') || (filter[feed.type] === 'On') || + ((filter[feed.type] === 'Friends') && (feed.isFriend)) || + ((filter[feed.type] === 'VIP') && (feed.isFavorite)))) { + filtered.push(feed); + } + } + }); + var notyToPlay = []; + filtered.forEach((feed) => { + var displayName = ''; + if (feed.displayName) { + displayName = feed.displayName; + } else if (feed.senderUsername) { + displayName = feed.senderUsername; + } else if (feed.data) { + displayName = feed.data; + } else { + console.error('missing displayName'); + } + if ((displayName) && (!this.notyMap[displayName]) || + (this.notyMap[displayName] < feed.created_at)) { + this.notyMap[displayName] = feed.created_at; + notyToPlay.push(feed); + } + }); + + // disable notifications when busy or game isn't running + if ((this.currentUserStatus === 'busy') || (!this.isGameRunning)) { + return; + } + notyToPlay.forEach(async (noty) => { + if (this.overlayNotificationsToggle) { + var text = ''; + switch (noty.type) { + case 'OnPlayerJoined': + text = `${noty.data} has joined`; + break; + case 'OnPlayerLeft': + text = `${noty.data} has left`; + break; + case 'OnPlayerJoining': + text = `${noty.data} is joining`; + break; + case 'GPS': + text = '' + noty.displayName + ' is in ' + await this.displayLocation(noty.location[0]); + break; + case 'Online': + text = `${noty.displayName} has logged in`; + break; + case 'Offline': + text = `${noty.displayName} has logged out`; + break; + case 'Status': + text = `${noty.displayName} status is now ${noty.status[0].status} ${noty.status[0].statusDescription}`; + break; + case 'invite': + text = `${noty.senderUsername} has invited you to ${noty.details.worldName}`; + break; + case 'requestInvite': + text = `${noty.senderUsername} has requested an invite`; + break; + case 'friendRequest': + text = `${noty.senderUsername} has sent you a friend request`; + break; + case 'Friend': + text = `${noty.displayName} is now your friend`; + break; + case 'Unfriend': + text = `${noty.displayName} has unfriended you`; + break; + case 'TrustLevel': + text = `${noty.displayName} trust level is now ${noty.trustLevel}`; + break; + case 'DisplayName': + text = `${noty.previousDisplayName} changed their name to ${noty.displayName}`; + break; + } + if (text) { + new Noty({ + type: 'alert', + theme: this.notificationTheme, + timeout: this.notificationTimeout, + layout: this.notificationPosition, + text: text + }).show(); + } + } + if (this.notificationTTSToggle) { + switch (noty.type) { + case 'OnPlayerJoined': + this.speak(`${noty.data} has joined`); + break; + case 'OnPlayerLeft': + this.speak(`${noty.data} has left`); + break; + case 'OnPlayerJoining': + this.speak(`${noty.data} is joining`); + break; + case 'GPS': + this.speak(noty.displayName + ' is in ' + await this.displayLocation(noty.location[0])); + break; + case 'Online': + this.speak(`${noty.displayName} has logged in`); + break; + case 'Offline': + this.speak(`${noty.displayName} has logged out`); + break; + case 'Status': + this.speak(`${noty.displayName} status is now ${noty.status[0].status} ${noty.status[0].statusDescription}`); + break; + case 'invite': + this.speak(`${noty.senderUsername} has invited you to ${noty.details.worldName}`); + break; + case 'requestInvite': + this.speak(`${noty.senderUsername} has requested an invite`); + break; + case 'friendRequest': + this.speak(`${noty.senderUsername} has sent you a friend request`); + break; + case 'Friend': + this.speak(`${noty.displayName} is now your friend`); + break; + case 'Unfriend': + this.speak(`${noty.displayName} has unfriended you`); + break; + case 'TrustLevel': + this.speak(`${noty.displayName} trust level is now ${noty.trustLevel}`); + break; + case 'DisplayName': + this.speak(`${noty.previousDisplayName} changed their name to ${noty.displayName}`); + break; + } + } + }); }; $app.methods.userStatusClass = function (user) { @@ -842,10 +946,40 @@ speechSynthesis.getVoices(); return style; }; + $app.methods.displayLocation = async function (location) { + var text = ''; + var L = API.parseLocation(location); + if (L.isOffline) { + text = 'Offline'; + } else if (L.isPrivate) { + text = 'Private'; + } else if (L.worldId) { + var ref = API.cachedWorlds.get(L.worldId); + if (ref === undefined) { + await API.getWorld({ + worldId: L.worldId + }).then((args) => { + if (L.tag === location) { + if (L.instanceId) { + text = `${args.json.name} ${L.accessType}`; + } else { + text = args.json.name; + } + } + }); + } else if (L.instanceId) { + text = `${ref.name} ${L.accessType}`; + } else { + text = ref.name; + } + } + return text; + }; + $app.methods.speak = function (text) { var tts = new SpeechSynthesisUtterance(); var voices = speechSynthesis.getVoices(); - var voiceIndex = configRepository.getString('VRCX_notificationTTSVoice'); + var voiceIndex = this.notificationTTSVoice; tts.voice = voices[voiceIndex]; tts.text = text; speechSynthesis.speak(tts); diff --git a/html/src/vr.pug b/html/src/vr.pug index d7b64431..6ce58908 100644 --- a/html/src/vr.pug +++ b/html/src/vr.pug @@ -12,11 +12,11 @@ html link(rel="stylesheet" href="https://fonts.googleapis.com/css?family=Noto+Sans+JP|Noto+Sans+KR&display=swap") link(rel="stylesheet" href="vr.css") body - .x-app#x-app(style="display:none" :class="{ 'x-app-type': appType === '1' }") + .x-app#x-app(v-if="appType === '1'" class="x-app-type") .x-container(style="flex:1") .x-friend-list(ref="list" style="color:#aaa") - template(v-if="isMinimalFeed === true") - template(v-for="feed in feeds") + template(v-if="isMinimalFeed") + template(v-for="feed in wristFeed") .x-friend-item(v-if="feed.type === 'GPS'" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra @@ -47,6 +47,12 @@ html span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} | ◀️ #[span.name(v-text="feed.data")] + div(v-else-if="feed.type === 'OnPlayerJoining'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") + .detail + span.extra + span.time {{ feed.created_at | formatDate('HH:MI') }} + span.spin ▶️ + span.name(v-text="feed.data" style="margin-left:20px") div(v-else-if="feed.type === 'Location'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra @@ -88,7 +94,7 @@ html span.time {{ feed.created_at | formatDate('HH:MI') }} | 🤝 #[span.name(v-text="feed.displayName")] {{ feed.previousTrustLevel }} #[i.el-icon-right] {{ feed.trustLevel }} template(v-else) - template(v-for="feed in feeds") + template(v-for="feed in wristFeed") .x-friend-item(v-if="feed.type === 'GPS'" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra @@ -119,6 +125,11 @@ html span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} | #[span.name(v-text="feed.data")] has left + div(v-else-if="feed.type === 'OnPlayerJoining'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") + .detail + span.extra + span.time {{ feed.created_at | formatDate('HH:MI') }} + | #[span.name(v-text="feed.data")] is joining div(v-else-if="feed.type === 'Location'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra diff --git a/html/src/vr.scss b/html/src/vr.scss index fc85bf76..711d9fc1 100644 --- a/html/src/vr.scss +++ b/html/src/vr.scss @@ -274,3 +274,19 @@ i.x-user-status.joinme { i.x-user-status.busy { background: #f56c6c; } + +.spin { + animation: rotation 2.5s infinite linear; + position: absolute; + width: 14px; + height: 28px; +} + +@keyframes rotation { + from { + transform: rotate(0deg); + } + to { + transform: rotate(359deg); + } +}