diff --git a/src/api/user.js b/src/api/user.js index 24a2a1e7..eef88830 100644 --- a/src/api/user.js +++ b/src/api/user.js @@ -168,6 +168,44 @@ const userReq = { }; return args; }); + }, + + getMutualCounts(params) { + return request(`users/${params.userId}/mutuals`, { + method: 'GET' + }).then((json) => { + const args = { + json, + params + }; + return args; + }); + }, + + getMutualFriends(params) { + return request(`users/${params.userId}/mutuals/friends`, { + method: 'GET', + params + }).then((json) => { + const args = { + json, + params + }; + return args; + }); + }, + + getMutualGroups(params) { + return request(`users/${params.userId}/mutuals/groups`, { + method: 'GET', + params + }).then((json) => { + const args = { + json, + params + }; + return args; + }); } }; diff --git a/src/app.scss b/src/app.scss index cc6ac23b..1a1a20ff 100644 --- a/src/app.scss +++ b/src/app.scss @@ -707,9 +707,9 @@ i.x-status-icon.red { border-color: rgb(255, 208, 0) !important; } -.x-tag-vrcplus { - color: rgb(255, 208, 0); - border-color: rgb(255, 208, 0) !important; +.x-tag-mutual-friend { + color: #67c23a; + border-color: #67c23a !important; } .x-tag-platform-pc { diff --git a/src/components/dialogs/UserDialog/UserDialog.vue b/src/components/dialogs/UserDialog/UserDialog.vue index 46da08f3..dfb31a3f 100644 --- a/src/components/dialogs/UserDialog/UserDialog.vue +++ b/src/components/dialogs/UserDialog/UserDialog.vue @@ -514,6 +514,73 @@ + +
+
+ + + {{ + t('dialog.user.groups.total_count', { count: userDialog.mutualFriends.length }) + }} +
+
+ {{ t('dialog.user.groups.sort_by') }} + + + {{ t(userDialog.mutualFriendSorting.name) }} + + + + + +
+
+ +
+
@@ -1253,6 +1320,9 @@ import { useI18n } from 'vue-i18n'; import { + compareByDisplayName, + compareByFriendOrder, + compareByLastActiveRef, compareByMemberCount, compareByName, copyToClipboard, @@ -1298,9 +1368,9 @@ } from '../../../api'; import { getNextDialogIndex, redirectToToolsTab } from '../../../shared/utils/base/ui'; import { processBulk, request } from '../../../service/request'; + import { userDialogGroupSortingOptions, userDialogMutualFriendSortingOptions } from '../../../shared/constants'; import { userDialogWorldOrderOptions, userDialogWorldSortingOptions } from '../../../shared/constants/'; import { database } from '../../../service/database'; - import { userDialogGroupSortingOptions } from '../../../shared/constants'; import SendInviteDialog from '../InviteDialog/SendInviteDialog.vue'; import UserSummaryHeader from './UserSummaryHeader.vue'; @@ -1376,6 +1446,7 @@ const userDialogGroupEditSelectedGroupIds = ref([]); // selected groups in edit mode const userDialogLastActiveTab = ref('Info'); + const userDialogLastMutualFriends = ref(''); const userDialogLastGroup = ref(''); const userDialogLastAvatar = ref(''); const userDialogLastWorld = ref(''); @@ -1499,6 +1570,15 @@ if (vrchatCredit.value === null) { getVRChatCredits(); } + } else if (tabName === 'Mutual Friends') { + if (userId === currentUser.value.id) { + userDialogLastActiveTab.value = 'Info'; + return; + } + if (userDialogLastMutualFriends.value !== userId) { + userDialogLastMutualFriends.value = userId; + getUserMutualFriends(userId); + } } else if (tabName === 'Groups') { if (userDialogLastGroup.value !== userId) { userDialogLastGroup.value = userId; @@ -2083,6 +2163,38 @@ userDialog.value.isGroupsLoading = false; } + async function getUserMutualFriends(userId) { + userDialog.value.isMutualFriendsLoading = true; + userDialog.value.mutualFriends = []; + const params = { + userId, + n: 100, + offset: 0 + }; + processBulk({ + fn: userRequest.getMutualFriends, + N: -1, + params, + handle: (args) => { + for (const json of args.json) { + if (userDialog.value.mutualFriends.some((u) => u.id === json.id)) { + continue; + } + const ref = cachedUsers.get(json.id); + if (typeof ref !== 'undefined') { + userDialog.value.mutualFriends.push(ref); + } else { + userDialog.value.mutualFriends.push(json); + } + } + setUserDialogMutualFriendSorting(userDialog.value.mutualFriendSorting); + }, + done: () => { + userDialog.value.isMutualFriendsLoading = false; + } + }); + } + function sortGroupsByInGame(a, b) { const aIndex = inGameGroupOrder.value.indexOf(a?.id); const bIndex = inGameGroupOrder.value.indexOf(b?.id); @@ -2347,6 +2459,22 @@ await sortCurrentUserGroups(); } + async function setUserDialogMutualFriendSorting(sortOrder) { + const D = userDialog.value; + D.mutualFriendSorting = sortOrder; + switch (sortOrder.value) { + case 'alphabetical': + D.mutualFriends.sort(compareByDisplayName); + break; + case 'lastActive': + D.mutualFriends.sort(compareByLastActiveRef); + break; + case 'friendOrder': + D.mutualFriends.sort(compareByFriendOrder); + break; + } + } + async function exitEditModeCurrentUserGroups() { userDialogGroupEditMode.value = false; userDialogGroupEditGroups.value = []; diff --git a/src/components/dialogs/UserDialog/UserSummaryHeader.vue b/src/components/dialogs/UserDialog/UserSummaryHeader.vue index 8976cd0c..0318485a 100644 --- a/src/components/dialogs/UserDialog/UserSummaryHeader.vue +++ b/src/components/dialogs/UserDialog/UserSummaryHeader.vue @@ -116,6 +116,20 @@ {{ userDialog.ref.$friendNumber ? userDialog.ref.$friendNumber : '' }} + + + + {{ userDialog.mutualFriendCount }} + + { name: 'dialog.user.groups.sorting.alphabetical', value: 'alphabetical' }, + mutualFriendSorting: { + name: 'dialog.user.mutual_friends.sorting.alphabetical', + value: 'alphabetical' + }, avatarSorting: 'update', avatarReleaseStatus: 'all', treeData: [], @@ -257,7 +261,11 @@ export const useUserStore = defineStore('User', () => { previousDisplayNames: [], dateFriended: '', unFriended: false, - dateFriendedInfo: [] + dateFriendedInfo: [], + mutualFriendCount: 0, + mutualGroupCount: 0, + mutualFriends: [], + isMutualFriendsLoading: false }); const currentTravelers = reactive(new Map()); @@ -813,6 +821,8 @@ export const useUserStore = defineStore('User', () => { D.dateFriended = ''; D.unFriended = false; D.dateFriendedInfo = []; + D.mutualFriendCount = 0; + D.mutualGroupCount = 0; if (userId === currentUser.value.id) { getWorldName(currentUser.value.homeLocation).then((worldName) => { D.$homeLocationName = worldName; @@ -951,6 +961,18 @@ export const useUserStore = defineStore('User', () => { D.isShowAvatar = true; } }); + if (D.isFriend) { + userRequest + .getMutualCounts({ userId }) + .then((args) => { + if (args.params.userId === D.id) { + D.mutualFriendCount = + args.json.friends; + D.mutualGroupCount = + args.json.groups; + } + }); + } } else { D.previousDisplayNames = currentUser.value.pastDisplayNames;