Mutual friends on friendsList

This commit is contained in:
Natsumi
2025-11-20 16:30:26 +11:00
parent b0621c04b8
commit 506b709a21
7 changed files with 82 additions and 4 deletions
@@ -2214,8 +2214,12 @@
} }
setUserDialogMutualFriendSorting(userDialog.value.mutualFriendSorting); setUserDialogMutualFriendSorting(userDialog.value.mutualFriendSorting);
}, },
done: () => { done: (success) => {
userDialog.value.isMutualFriendsLoading = false; userDialog.value.isMutualFriendsLoading = false;
if (success) {
const mutualIds = userDialog.value.mutualFriends.map((u) => u.id);
database.updateMutualsForFriend(userId, mutualIds);
}
} }
}); });
} }
+4 -2
View File
@@ -273,7 +273,8 @@
"filter_placeholder": "Filter", "filter_placeholder": "Filter",
"refresh_tooltip": "Refresh", "refresh_tooltip": "Refresh",
"clear_tooltip": "Clear Results", "clear_tooltip": "Clear Results",
"cancel_tooltip": "Cancel" "cancel_tooltip": "Cancel",
"load_mutual_friends": "Load Mutual Friends"
}, },
"charts": { "charts": {
"header": "Charts", "header": "Charts",
@@ -2201,7 +2202,8 @@
"lastActivity": "Last Activity", "lastActivity": "Last Activity",
"lastLogin": "Last Login", "lastLogin": "Last Login",
"dateJoined": "Date Joined", "dateJoined": "Date Joined",
"unfriend": "Unfriend" "unfriend": "Unfriend",
"mutualFriends": "Mutual Friends"
}, },
"profile": { "profile": {
"invite_messages": { "invite_messages": {
+45
View File
@@ -86,6 +86,51 @@ const mutualGraph = {
await sqliteService.executeNonQuery('ROLLBACK'); await sqliteService.executeNonQuery('ROLLBACK');
throw err; throw err;
} }
},
async updateMutualsForFriend(friendId, mutualIds) {
if (!dbVars.userPrefix || !friendId) {
return;
}
const friendTable = `${dbVars.userPrefix}_mutual_graph_friends`;
const linkTable = `${dbVars.userPrefix}_mutual_graph_links`;
const safeFriendId = friendId.replace(/'/g, "''");
await sqliteService.executeNonQuery(
`INSERT OR REPLACE INTO ${friendTable} (friend_id) VALUES ('${safeFriendId}')`
);
await sqliteService.executeNonQuery(
`DELETE FROM ${linkTable} WHERE friend_id='${safeFriendId}'`
);
let edgeValues = '';
for (const mutual of mutualIds) {
if (!mutual) {
continue;
}
const safeMutualId = String(mutual).replace(/'/g, "''");
edgeValues += `('${safeFriendId}', '${safeMutualId}'),`;
}
if (edgeValues) {
edgeValues = edgeValues.slice(0, -1);
await sqliteService.executeNonQuery(
`INSERT OR REPLACE INTO ${linkTable} (friend_id, mutual_id) VALUES ${edgeValues}`
);
}
},
async getMutualCountForAllUsers() {
const mutualCountMap = new Map();
if (!dbVars.userPrefix) {
return mutualCountMap;
}
const linkTable = `${dbVars.userPrefix}_mutual_graph_links`;
await sqliteService.execute((dbRow) => {
const mutualId = dbRow[0];
const count = dbRow[1];
if (mutualId) {
mutualCountMap.set(mutualId, count);
}
}, `SELECT mutual_id, COUNT(*) FROM ${linkTable} GROUP BY mutual_id`);
return mutualCountMap;
} }
}; };
+11
View File
@@ -915,6 +915,16 @@ export const useFriendStore = defineStore('Friend', () => {
} }
} }
async function getAllUserMutualCount() {
const mutualCountMap = await database.getMutualCountForAllUsers();
for (const [userId, mutualCount] of mutualCountMap.entries()) {
const ref = friends.get(userId);
if (ref?.ref) {
ref.ref.$mutualCount = mutualCount;
}
}
}
/** /**
* *
* @param {string} id * @param {string} id
@@ -1625,6 +1635,7 @@ export const useFriendStore = defineStore('Friend', () => {
refreshFriendsList, refreshFriendsList,
updateOnlineFriendCounter, updateOnlineFriendCounter,
getAllUserStats, getAllUserStats,
getAllUserMutualCount,
initFriendLog, initFriendLog,
migrateFriendLog, migrateFriendLog,
getFriendLog, getFriendLog,
+1
View File
@@ -523,6 +523,7 @@ export const useUserStore = defineStore('User', () => {
$joinCount: 0, $joinCount: 0,
$timeSpent: 0, $timeSpent: 0,
$lastSeen: '', $lastSeen: '',
$mutualCount: 0,
$nickName: '', $nickName: '',
$previousLocation: '', $previousLocation: '',
$customTag: '', $customTag: '',
+1
View File
@@ -55,6 +55,7 @@ export interface VrcxUser extends GetUserResponse {
$joinCount: number; $joinCount: number;
$timeSpent: number; $timeSpent: number;
$lastSeen: string; $lastSeen: string;
$mutualCount: number;
$nickName: string; $nickName: string;
$previousLocation: string; $previousLocation: string;
$customTag: string; $customTag: string;
+15 -1
View File
@@ -4,6 +4,9 @@
<div style="display: flex; align-items: center; justify-content: space-between"> <div style="display: flex; align-items: center; justify-content: space-between">
<span class="header">{{ t('view.friend_list.header') }}</span> <span class="header">{{ t('view.friend_list.header') }}</span>
<div style="font-size: 13px; display: flex; align-items: center"> <div style="font-size: 13px; display: flex; align-items: center">
<el-button size="small" @click="openChartsTab" style="margin-right: 10px">
{{ t('view.friend_list.load_mutual_friends') }}
</el-button>
<div v-if="friendsListBulkUnfriendMode" style="display: inline-block; margin-right: 10px"> <div v-if="friendsListBulkUnfriendMode" style="display: inline-block; margin-right: 10px">
<el-button size="small" @click="showBulkUnfriendSelectionConfirm"> <el-button size="small" @click="showBulkUnfriendSelectionConfirm">
{{ t('view.friend_list.bulk_unfriend_selection') }} {{ t('view.friend_list.bulk_unfriend_selection') }}
@@ -214,6 +217,11 @@
<span>{{ formatDateFilter(row.$lastSeen, 'long') }}</span> <span>{{ formatDateFilter(row.$lastSeen, 'long') }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('table.friendList.mutualFriends')" width="120" prop="$mutualCount" sortable>
<template #default="{ row }">
<span v-if="row.$mutualCount">{{ row.$mutualCount }}</span>
<span v-else></span> </template
></el-table-column>
<el-table-column <el-table-column
:label="t('table.friendList.lastActivity')" :label="t('table.friendList.lastActivity')"
width="170" width="170"
@@ -284,13 +292,14 @@
} from '../../stores'; } from '../../stores';
import { friendRequest, userRequest } from '../../api'; import { friendRequest, userRequest } from '../../api';
import removeConfusables, { removeWhitespace } from '../../service/confusables'; import removeConfusables, { removeWhitespace } from '../../service/confusables';
import { router } from '../../plugin/router';
const { t } = useI18n(); const { t } = useI18n();
const emit = defineEmits(['lookup-user']); const emit = defineEmits(['lookup-user']);
const { friends } = storeToRefs(useFriendStore()); const { friends } = storeToRefs(useFriendStore());
const { getAllUserStats, confirmDeleteFriend, handleFriendDelete } = useFriendStore(); const { getAllUserStats, getAllUserMutualCount, confirmDeleteFriend, handleFriendDelete } = useFriendStore();
const { randomUserColours } = storeToRefs(useAppearanceSettingsStore()); const { randomUserColours } = storeToRefs(useAppearanceSettingsStore());
const { showUserDialog } = useUserStore(); const { showUserDialog } = useUserStore();
const { stringComparer, friendsListSearch } = storeToRefs(useSearchStore()); const { stringComparer, friendsListSearch } = storeToRefs(useSearchStore());
@@ -363,6 +372,7 @@
results.push(ctx.ref); results.push(ctx.ref);
} }
getAllUserStats(); getAllUserStats();
getAllUserMutualCount();
nextTick(() => { nextTick(() => {
friendsListTable.data = results; friendsListTable.data = results;
friendsListLoading.value = false; friendsListLoading.value = false;
@@ -449,4 +459,8 @@
const bs = b.$languages.map((i) => i.value).sort(); const bs = b.$languages.map((i) => i.value).sort();
return JSON.stringify(as).localeCompare(JSON.stringify(bs)); return JSON.stringify(as).localeCompare(JSON.stringify(bs));
} }
function openChartsTab() {
router.push({ name: 'charts' });
}
</script> </script>