mirror of
https://github.com/vrcx-team/VRCX.git
synced 2026-04-06 00:32:02 +02:00
fix: load mutual friends button and mutual opt-out status in friend list
This commit is contained in:
@@ -2861,7 +2861,8 @@
|
||||
"lastLogin": "Last Login",
|
||||
"dateJoined": "Date Joined",
|
||||
"unfriend": "Unfriend",
|
||||
"mutualFriends": "Mutual Friends"
|
||||
"mutualFriends": "Mutual Friends",
|
||||
"mutualOptedOut": "This user has disabled mutual friends sharing. Showing cached data."
|
||||
},
|
||||
"profile": {
|
||||
"invite_messages": {
|
||||
|
||||
@@ -52,6 +52,21 @@ const mutualGraph = {
|
||||
await sqliteService.executeNonQuery('COMMIT');
|
||||
return;
|
||||
}
|
||||
// Also clean links for friends in the new entries even if they
|
||||
// were previously opted_out. We have fresh data for them now so
|
||||
// old links must not linger.
|
||||
let idsToClean = '';
|
||||
pairs.forEach((_, friendId) => {
|
||||
if (!friendId) return;
|
||||
const safe = friendId.replace(/'/g, "''");
|
||||
idsToClean += `'${safe}',`;
|
||||
});
|
||||
if (idsToClean) {
|
||||
idsToClean = idsToClean.slice(0, -1);
|
||||
await sqliteService.executeNonQuery(
|
||||
`DELETE FROM ${linkTable} WHERE friend_id IN (${idsToClean})`
|
||||
);
|
||||
}
|
||||
let friendValues = '';
|
||||
let edgeValues = '';
|
||||
pairs.forEach((mutualIds, friendId) => {
|
||||
|
||||
@@ -234,6 +234,7 @@ export function createDefaultUserRef(json) {
|
||||
$timeSpent: 0,
|
||||
$lastSeen: '',
|
||||
$mutualCount: 0,
|
||||
$mutualOptedOut: false,
|
||||
$nickName: '',
|
||||
$previousLocation: '',
|
||||
$customTag: '',
|
||||
|
||||
@@ -322,6 +322,14 @@ export const useChartsStore = defineStore('Charts', () => {
|
||||
mutualGraphStatus.friendSignature = friendCount.value;
|
||||
mutualGraphStatus.needsRefetch = false;
|
||||
|
||||
// Write meta first so saveMutualGraphSnapshot's DELETE
|
||||
// uses up-to-date opted_out flags to decide what to preserve.
|
||||
// If this fails, we must NOT proceed with snapshot save because
|
||||
// the DELETE would use stale meta and corrupt data.
|
||||
if (metaEntries.size > 0) {
|
||||
await database.bulkUpsertMutualGraphMeta(metaEntries);
|
||||
}
|
||||
|
||||
try {
|
||||
const entries = new Map();
|
||||
mutualMap.forEach((value, friendId) => {
|
||||
@@ -348,17 +356,6 @@ export const useChartsStore = defineStore('Charts', () => {
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (metaEntries.size > 0) {
|
||||
await database.bulkUpsertMutualGraphMeta(metaEntries);
|
||||
}
|
||||
} catch (metaErr) {
|
||||
console.error(
|
||||
'[MutualNetworkGraph] Failed to write meta',
|
||||
metaErr
|
||||
);
|
||||
}
|
||||
|
||||
markMutualGraphLoaded({ notify: true });
|
||||
return mutualMap;
|
||||
} catch (err) {
|
||||
|
||||
@@ -61,6 +61,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
let pendingSortedFriendsRebuild = false;
|
||||
let allUserStatsRequestId = 0;
|
||||
let allUserMutualCountRequestId = 0;
|
||||
let allUserMutualOptedOutRequestId = 0;
|
||||
|
||||
const derivedDebugCounters = reactive({
|
||||
allFavoriteFriendIds: 0,
|
||||
@@ -904,6 +905,11 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
return;
|
||||
}
|
||||
runInSortedFriendsBatch(() => {
|
||||
for (const ctx of friends.values()) {
|
||||
if (ctx?.ref) {
|
||||
ctx.ref.$mutualCount = 0;
|
||||
}
|
||||
}
|
||||
for (const [userId, mutualCount] of mutualCountMap.entries()) {
|
||||
const ref = friends.get(userId);
|
||||
if (ref?.ref) {
|
||||
@@ -914,6 +920,33 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
async function getAllUserMutualOptedOut() {
|
||||
if (!friends.size) {
|
||||
return;
|
||||
}
|
||||
const requestId = ++allUserMutualOptedOutRequestId;
|
||||
const metaMap = await database.getMutualGraphMeta();
|
||||
if (requestId !== allUserMutualOptedOutRequestId) {
|
||||
return;
|
||||
}
|
||||
runInSortedFriendsBatch(() => {
|
||||
for (const ctx of friends.values()) {
|
||||
if (ctx?.ref) {
|
||||
ctx.ref.$mutualOptedOut = false;
|
||||
}
|
||||
}
|
||||
for (const [userId, meta] of metaMap.entries()) {
|
||||
const ref = friends.get(userId);
|
||||
if (ref?.ref) {
|
||||
ref.ref.$mutualOptedOut = Boolean(meta.optedOut);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id
|
||||
@@ -1398,6 +1431,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
updateOnlineFriendCounter,
|
||||
getAllUserStats,
|
||||
getAllUserMutualCount,
|
||||
getAllUserMutualOptedOut,
|
||||
initFriendLog,
|
||||
migrateFriendLog,
|
||||
getFriendLog,
|
||||
|
||||
1
src/types/api/user.d.ts
vendored
1
src/types/api/user.d.ts
vendored
@@ -56,6 +56,7 @@ export interface VrcxUser extends GetUserResponse {
|
||||
$timeSpent: number;
|
||||
$lastSeen: string;
|
||||
$mutualCount: number;
|
||||
$mutualOptedOut: boolean;
|
||||
$nickName: string;
|
||||
$previousLocation: string;
|
||||
$customTag: string;
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
@update:modelValue="toggleFriendsListBulkUnfriendMode" />
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<Button variant="outline" class="mr-2" @click="openChartsTab">
|
||||
<Button variant="outline" class="mr-2" :disabled="isMutualFetching || isMutualOptOut" @click="loadMutualFriends">
|
||||
{{ t('view.friend_list.load_mutual_friends') }}
|
||||
</Button>
|
||||
|
||||
@@ -133,9 +133,11 @@
|
||||
|
||||
import {
|
||||
useAppearanceSettingsStore,
|
||||
useChartsStore,
|
||||
useFriendStore,
|
||||
useModalStore,
|
||||
useSearchStore
|
||||
useSearchStore,
|
||||
useUserStore
|
||||
} from '../../stores';
|
||||
import { friendRequest, userRequest } from '../../api';
|
||||
import { DataTableLayout } from '../../components/ui/data-table';
|
||||
@@ -144,7 +146,6 @@
|
||||
import { createColumns } from './columns.jsx';
|
||||
import { localeIncludes } from '../../shared/utils';
|
||||
import removeConfusables, { removeWhitespace } from '../../services/confusables';
|
||||
import { router } from '../../plugins/router';
|
||||
import { useVrcxVueTable } from '../../lib/table/useVrcxVueTable';
|
||||
import { showUserDialog } from '../../coordinators/userCoordinator';
|
||||
import { confirmDeleteFriend, handleFriendDelete } from '../../coordinators/friendRelationshipCoordinator';
|
||||
@@ -156,7 +157,10 @@
|
||||
|
||||
const { friends, allFavoriteFriendIds } = storeToRefs(useFriendStore());
|
||||
const modalStore = useModalStore();
|
||||
const { getAllUserStats, getAllUserMutualCount } = useFriendStore();
|
||||
const { getAllUserStats, getAllUserMutualCount, getAllUserMutualOptedOut } = useFriendStore();
|
||||
const chartsStore = useChartsStore();
|
||||
const isMutualFetching = computed(() => chartsStore.mutualGraphStatus.isFetching);
|
||||
const isMutualOptOut = computed(() => Boolean(useUserStore().currentUser?.hasSharedConnectionsOptOut));
|
||||
const appearanceSettingsStore = useAppearanceSettingsStore();
|
||||
const { randomUserColours } = storeToRefs(appearanceSettingsStore);
|
||||
const { userImage } = useUserDisplay();
|
||||
@@ -303,7 +307,8 @@
|
||||
}
|
||||
friendStatsRefreshInFlight = Promise.allSettled([
|
||||
getAllUserStats(),
|
||||
getAllUserMutualCount()
|
||||
getAllUserMutualCount(),
|
||||
getAllUserMutualOptedOut()
|
||||
]).then((results) => {
|
||||
if (results.every((result) => result.status === 'fulfilled')) {
|
||||
lastFriendStatsRefreshAt = Date.now();
|
||||
@@ -556,8 +561,13 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function openChartsTab() {
|
||||
router.push({ name: 'charts' });
|
||||
async function loadMutualFriends() {
|
||||
if (isMutualFetching.value) return;
|
||||
await chartsStore.fetchMutualGraph();
|
||||
await Promise.allSettled([
|
||||
getAllUserMutualCount(),
|
||||
getAllUserMutualOptedOut()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ArrowUpDown, User, UserMinus } from 'lucide-vue-next';
|
||||
import { ArrowUpDown, EyeOff, User, UserMinus } from 'lucide-vue-next';
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
@@ -391,7 +391,21 @@ export const createColumns = ({
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const count = row.original?.$mutualCount;
|
||||
return count ? <span>{count}</span> : null;
|
||||
const optedOut = row.original?.$mutualOptedOut;
|
||||
if (!count && !optedOut) return null;
|
||||
return (
|
||||
<span class="inline-flex items-center gap-1">
|
||||
{count || null}
|
||||
{optedOut ? (
|
||||
<TooltipWrapper
|
||||
side="top"
|
||||
content={t('table.friendList.mutualOptedOut')}
|
||||
>
|
||||
<EyeOff class="h-3.5 w-3.5 text-muted-foreground" />
|
||||
</TooltipWrapper>
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user