diff --git a/html/src/app.js b/html/src/app.js index e5e49d2d..786aed88 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -4981,6 +4981,11 @@ speechSynthesis.getVoices(); return compareByActivityField(a, b, 'last_activity'); }; + // last seen + var compareByLastSeen = function (a, b) { + return compareByActivityField(a, b, '$lastSeen'); + }; + var getFriendsSortFunction = function (sortMethods) { const sorts = []; for (const sortMethod of sortMethods) { @@ -4997,6 +5002,9 @@ speechSynthesis.getVoices(); case 'Sort by Last Active': sorts.push(compareByLastActive); break; + case 'Sort by Last Seen': + sorts.push(compareByLastSeen); + break; case 'Sort by Time in Instance': sorts.push((a, b) => { if ( @@ -5392,7 +5400,7 @@ speechSynthesis.getVoices(); ); // eslint-disable-next-line require-atomic-updates $app.feedSessionTable = await database.getFeedDatabase(); - $app.feedTableLookup(); + await $app.feedTableLookup(); // eslint-disable-next-line require-atomic-updates $app.notificationTable.data = await database.getNotifications(); await this.refreshNotifications(); @@ -5419,18 +5427,19 @@ speechSynthesis.getVoices(); throw err; } } - $app.sortVIPFriends = true; - $app.sortOnlineFriends = true; - $app.sortActiveFriends = true; - $app.sortOfflineFriends = true; - $app.getAvatarHistory(); - $app.getAllUserMemos(); + await $app.getAvatarHistory(); + await $app.getAllUserMemos(); if ($app.randomUserColours) { $app.getNameColour(this.currentUser.id).then((colour) => { this.currentUser.$userColour = colour; }); - $app.userColourInit(); + await $app.userColourInit(); } + await $app.getAllUserStats(); + $app.sortVIPFriends = true; + $app.sortOnlineFriends = true; + $app.sortActiveFriends = true; + $app.sortOfflineFriends = true; this.getAuth(); $app.updateSharedFeed(true); if ($app.isGameRunning) { @@ -8100,6 +8109,7 @@ speechSynthesis.getVoices(); ); this.friendLogTable.filters[2].value = this.hideUnfriends; }; + $app.data.notificationTTSTest = ''; $app.data.TTSvoices = speechSynthesis.getVoices(); $app.methods.saveNotificationTTS = async function () { speechSynthesis.cancel(); @@ -8116,6 +8126,10 @@ speechSynthesis.getVoices(); ); this.updateVRConfigVars(); }; + $app.methods.testNotificationTTS = function () { + speechSynthesis.cancel(); + this.speak(this.notificationTTSTest); + }; $app.data.themeMode = await configRepository.getString( 'VRCX_ThemeMode', 'system' @@ -14695,7 +14709,7 @@ speechSynthesis.getVoices(); this.friendsListTable.data = results; }; - $app.methods.getAllUserStats = function () { + $app.methods.getAllUserStats = async function () { var userIds = []; var displayNames = []; for (var ctx of this.friends.values()) { @@ -14705,55 +14719,54 @@ speechSynthesis.getVoices(); } } - database.getAllUserStats(userIds, displayNames).then((data) => { - var friendListMap = new Map(); - for (var item of data) { - if (!item.userId) { - // find userId from previous data with matching displayName - for (var ref of data) { - if ( - ref.displayName === item.displayName && - ref.userId - ) { - item.userId = ref.userId; - } - } - // if still no userId, find userId from friends list - if (!item.userId) { - for (var ref of this.friends.values()) { - if ( - ref?.ref?.id && - ref.ref.displayName === item.displayName - ) { - item.userId = ref.id; - } - } - } - // if still no userId, skip - if (!item.userId) { - continue; + var data = await database.getAllUserStats(userIds, displayNames); + var friendListMap = new Map(); + for (var item of data) { + if (!item.userId) { + // find userId from previous data with matching displayName + for (var ref of data) { + if (ref.displayName === item.displayName && ref.userId) { + item.userId = ref.userId; } } - - var friend = friendListMap.get(item.userId); - if (!friend) { - friendListMap.set(item.userId, item); + // if still no userId, find userId from friends list + if (!item.userId) { + for (var ref of this.friends.values()) { + if ( + ref?.ref?.id && + ref.ref.displayName === item.displayName + ) { + item.userId = ref.id; + } + } + } + // if still no userId, skip + if (!item.userId) { continue; } - friend.timeSpent += item.timeSpent; - friend.joinCount += item.joinCount; - friend.displayName = item.displayName; - friendListMap.set(item.userId, friend); } - for (var item of friendListMap.values()) { - var ref = this.friends.get(item.userId); - if (ref?.ref) { - ref.ref.$joinCount = item.joinCount; - ref.ref.$lastSeen = item.created_at; - ref.ref.$timeSpent = item.timeSpent; - } + + var friend = friendListMap.get(item.userId); + if (!friend) { + friendListMap.set(item.userId, item); + continue; } - }); + if (Date.parse(item.lastSeen) > Date.parse(friend.lastSeen)) { + friend.lastSeen = item.lastSeen; + } + friend.timeSpent += item.timeSpent; + friend.joinCount += item.joinCount; + friend.displayName = item.displayName; + friendListMap.set(item.userId, friend); + } + for (var item of friendListMap.values()) { + var ref = this.friends.get(item.userId); + if (ref?.ref) { + ref.ref.$joinCount = item.joinCount; + ref.ref.$lastSeen = item.lastSeen; + ref.ref.$timeSpent = item.timeSpent; + } + } }; $app.methods.getUserStats = async function (ctx) { diff --git a/html/src/classes/gameLog.js b/html/src/classes/gameLog.js index 3a1665c8..d1942743 100644 --- a/html/src/classes/gameLog.js +++ b/html/src/classes/gameLog.js @@ -163,6 +163,20 @@ export default class extends baseClass { if (typeof ref === 'undefined') { break; } + var friendRef = this.friends.get(userId); + if (typeof friendRef?.ref !== 'undefined') { + friendRef.ref.$joinCount++; + friendRef.ref.$lastSeen = Date.now(); + friendRef.ref.$timeSpent += Date.now() - ref.joinTime; + if ( + this.sidebarSortMethods.includes( + 'Sort by Last Seen' + ) + ) { + this.sortVIPFriends = true; + this.sortOnlineFriends = true; + } + } var time = Date.now() - ref.joinTime; this.lastLocation.playerList.delete(userId); this.lastLocation.friendList.delete(userId); @@ -410,7 +424,10 @@ export default class extends baseClass { break; } - $app.trySaveStickerToFile(gameLog.displayName, gameLog.fileId); + $app.trySaveStickerToFile( + gameLog.displayName, + gameLog.fileId + ); break; } if (entry) { diff --git a/html/src/localization/en/en.json b/html/src/localization/en/en.json index 67b5b82b..3c2ad6c2 100644 --- a/html/src/localization/en/en.json +++ b/html/src/localization/en/en.json @@ -309,6 +309,7 @@ "status_and_private_to_bottom": "Status and Private to Bottom", "location": "Location", "last_active": "Last Active", + "last_seen": "Last Seen", "time_in_instance": "Time in Instance", "placeholder": "Sort Order", "dropdown_header": "Choose Sort Order" @@ -367,7 +368,9 @@ "when_to_play_game_running": "Game Running", "when_to_play_always": "Always", "tts_voice": "TTS Voice", - "use_memo_nicknames": "Use Memo Nicknames" + "use_memo_nicknames": "Use Memo Nicknames", + "play": "Play", + "tts_test_placeholder": "Test TTS" } } }, diff --git a/html/src/mixins/dialogs/userDialog.pug b/html/src/mixins/dialogs/userDialog.pug index 3fd48fee..011a36f3 100644 --- a/html/src/mixins/dialogs/userDialog.pug +++ b/html/src/mixins/dialogs/userDialog.pug @@ -64,18 +64,19 @@ mixin userDialog() span(v-if="badge.hidden")  (Hidden) el-popover(placement="right" width="300px" trigger="click") img.x-link.x-user-badge(slot="reference" v-lazy="badge.badgeImageUrl" style="flex:none;height:32px;width:32px;border-radius:3px;object-fit:cover;margin-top:5px;margin-right:5px" :class="{'x-user-badge-hidden':badge.hidden}") - img.x-link(v-lazy="badge.badgeImageUrl" style="height:300px" @click="showFullscreenImageDialog(badge.badgeImageUrl)") + img.x-link(v-lazy="badge.badgeImageUrl" style="width:300px" @click="showFullscreenImageDialog(badge.badgeImageUrl)") br - span {{ badge.badgeName }} - br - span.x-grey(style="font-size:12px") {{ badge.badgeDescription }} - br - span.x-grey(v-if="badge.assignedAt" style="font-family:monospace;font-size:12px") {{ $t('dialog.user.badges.assigned') }}: {{ badge.assignedAt | formatDate('long') }} - template(v-if="userDialog.id === API.currentUser.id") + div(style="display:block;width:300px;word-break:normal") + span {{ badge.badgeName }} br - el-checkbox(@change="toggleBadgeVisibility(badge)" v-model="badge.hidden" style="margin-top:5px") {{ $t('dialog.user.badges.hidden') }} + span.x-grey(style="font-size:12px") {{ badge.badgeDescription }} br - el-checkbox(@change="toggleBadgeShowcased(badge)" v-model="badge.showcased" style="margin-top:5px") {{ $t('dialog.user.badges.showcased') }} + span.x-grey(v-if="badge.assignedAt" style="font-family:monospace;font-size:12px") {{ $t('dialog.user.badges.assigned') }}: {{ badge.assignedAt | formatDate('long') }} + template(v-if="userDialog.id === API.currentUser.id") + br + el-checkbox(@change="toggleBadgeVisibility(badge)" v-model="badge.hidden" style="margin-top:5px") {{ $t('dialog.user.badges.hidden') }} + br + el-checkbox(@change="toggleBadgeShowcased(badge)" v-model="badge.showcased" style="margin-top:5px") {{ $t('dialog.user.badges.showcased') }} div(style="margin-top:5px") span(v-text="userDialog.ref.statusDescription" style="font-size:12px") div(v-if="userDialog.ref.userIcon" style="flex:none;margin-right:10px") diff --git a/html/src/mixins/tabs/settings.pug b/html/src/mixins/tabs/settings.pug index 53de1cb5..f7e1f6cc 100644 --- a/html/src/mixins/tabs/settings.pug +++ b/html/src/mixins/tabs/settings.pug @@ -217,6 +217,7 @@ mixin settingsTab() el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.status')" value="Sort by Status") el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.private_to_bottom')" value="Sort Private to Bottom") el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.last_active')" value="Sort by Last Active") + el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.last_seen')" value="Sort by Last Seen") el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.time_in_instance')" value="Sort by Time in Instance") el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.location')" value="Sort by Location") i.el-icon-arrow-right(style="margin:16px 5px") @@ -226,6 +227,7 @@ mixin settingsTab() el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.status')" value="Sort by Status") el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.private_to_bottom')" value="Sort Private to Bottom") el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.last_active')" value="Sort by Last Active") + el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.last_seen')" value="Sort by Last Seen") el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.time_in_instance')" value="Sort by Time in Instance") el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.location')" value="Sort by Location") i.el-icon-arrow-right(style="margin:16px 5px") @@ -235,6 +237,7 @@ mixin settingsTab() el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.status')" value="Sort by Status") el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.private_to_bottom')" value="Sort Private to Bottom") el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.last_active')" value="Sort by Last Active") + el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.last_seen')" value="Sort by Last Seen") el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.time_in_instance')" value="Sort by Time in Instance") el-option.x-friend-item(:label="$t('view.settings.appearance.side_panel.sorting.location')" value="Sort by Location") div.options-container-item @@ -358,6 +361,9 @@ mixin settingsTab() div.options-container-item span.name {{ $t('view.settings.notifications.notifications.text_to_speech.use_memo_nicknames') }} el-switch(v-model="notificationTTSNickName" @change="saveOpenVROption" :disabled="notificationTTS === 'Never'") + div.options-container-item + el-input(type="textarea" v-model="notificationTTSTest" :placeholder="$t('view.settings.notifications.notifications.text_to_speech.tts_test_placeholder')" :rows="1" style="width:175px;display:inline-block") + el-button(size="small" icon="el-icon-video-play" @click="testNotificationTTS") {{ $t('view.settings.notifications.notifications.text_to_speech.play') }} //- Wrist Overlay Tab el-tab-pane(:label="$t('view.settings.category.wrist_overlay')") //- Wrist Overlay | SteamVR Wrist Overlay diff --git a/html/src/repository/database.js b/html/src/repository/database.js index 1fd91380..46d7da53 100644 --- a/html/src/repository/database.js +++ b/html/src/repository/database.js @@ -1194,7 +1194,7 @@ class Database { await sqliteService.execute( (dbRow) => { var row = { - created_at: dbRow[0], + lastSeen: dbRow[0], userId: dbRow[1], timeSpent: dbRow[2], joinCount: dbRow[3],