mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-07 14:56:06 +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",
|
"lastLogin": "Last Login",
|
||||||
"dateJoined": "Date Joined",
|
"dateJoined": "Date Joined",
|
||||||
"unfriend": "Unfriend",
|
"unfriend": "Unfriend",
|
||||||
"mutualFriends": "Mutual Friends"
|
"mutualFriends": "Mutual Friends",
|
||||||
|
"mutualOptedOut": "This user has disabled mutual friends sharing. Showing cached data."
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"invite_messages": {
|
"invite_messages": {
|
||||||
|
|||||||
@@ -52,6 +52,21 @@ const mutualGraph = {
|
|||||||
await sqliteService.executeNonQuery('COMMIT');
|
await sqliteService.executeNonQuery('COMMIT');
|
||||||
return;
|
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 friendValues = '';
|
||||||
let edgeValues = '';
|
let edgeValues = '';
|
||||||
pairs.forEach((mutualIds, friendId) => {
|
pairs.forEach((mutualIds, friendId) => {
|
||||||
|
|||||||
@@ -234,6 +234,7 @@ export function createDefaultUserRef(json) {
|
|||||||
$timeSpent: 0,
|
$timeSpent: 0,
|
||||||
$lastSeen: '',
|
$lastSeen: '',
|
||||||
$mutualCount: 0,
|
$mutualCount: 0,
|
||||||
|
$mutualOptedOut: false,
|
||||||
$nickName: '',
|
$nickName: '',
|
||||||
$previousLocation: '',
|
$previousLocation: '',
|
||||||
$customTag: '',
|
$customTag: '',
|
||||||
|
|||||||
+8
-11
@@ -322,6 +322,14 @@ export const useChartsStore = defineStore('Charts', () => {
|
|||||||
mutualGraphStatus.friendSignature = friendCount.value;
|
mutualGraphStatus.friendSignature = friendCount.value;
|
||||||
mutualGraphStatus.needsRefetch = false;
|
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 {
|
try {
|
||||||
const entries = new Map();
|
const entries = new Map();
|
||||||
mutualMap.forEach((value, friendId) => {
|
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 });
|
markMutualGraphLoaded({ notify: true });
|
||||||
return mutualMap;
|
return mutualMap;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
let pendingSortedFriendsRebuild = false;
|
let pendingSortedFriendsRebuild = false;
|
||||||
let allUserStatsRequestId = 0;
|
let allUserStatsRequestId = 0;
|
||||||
let allUserMutualCountRequestId = 0;
|
let allUserMutualCountRequestId = 0;
|
||||||
|
let allUserMutualOptedOutRequestId = 0;
|
||||||
|
|
||||||
const derivedDebugCounters = reactive({
|
const derivedDebugCounters = reactive({
|
||||||
allFavoriteFriendIds: 0,
|
allFavoriteFriendIds: 0,
|
||||||
@@ -904,6 +905,11 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
runInSortedFriendsBatch(() => {
|
runInSortedFriendsBatch(() => {
|
||||||
|
for (const ctx of friends.values()) {
|
||||||
|
if (ctx?.ref) {
|
||||||
|
ctx.ref.$mutualCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
for (const [userId, mutualCount] of mutualCountMap.entries()) {
|
for (const [userId, mutualCount] of mutualCountMap.entries()) {
|
||||||
const ref = friends.get(userId);
|
const ref = friends.get(userId);
|
||||||
if (ref?.ref) {
|
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
|
* @param {string} id
|
||||||
@@ -1398,6 +1431,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
updateOnlineFriendCounter,
|
updateOnlineFriendCounter,
|
||||||
getAllUserStats,
|
getAllUserStats,
|
||||||
getAllUserMutualCount,
|
getAllUserMutualCount,
|
||||||
|
getAllUserMutualOptedOut,
|
||||||
initFriendLog,
|
initFriendLog,
|
||||||
migrateFriendLog,
|
migrateFriendLog,
|
||||||
getFriendLog,
|
getFriendLog,
|
||||||
|
|||||||
Vendored
+1
@@ -56,6 +56,7 @@ export interface VrcxUser extends GetUserResponse {
|
|||||||
$timeSpent: number;
|
$timeSpent: number;
|
||||||
$lastSeen: string;
|
$lastSeen: string;
|
||||||
$mutualCount: number;
|
$mutualCount: number;
|
||||||
|
$mutualOptedOut: boolean;
|
||||||
$nickName: string;
|
$nickName: string;
|
||||||
$previousLocation: string;
|
$previousLocation: string;
|
||||||
$customTag: string;
|
$customTag: string;
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
@update:modelValue="toggleFriendsListBulkUnfriendMode" />
|
@update:modelValue="toggleFriendsListBulkUnfriendMode" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<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') }}
|
{{ t('view.friend_list.load_mutual_friends') }}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
@@ -133,9 +133,11 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
useAppearanceSettingsStore,
|
useAppearanceSettingsStore,
|
||||||
|
useChartsStore,
|
||||||
useFriendStore,
|
useFriendStore,
|
||||||
useModalStore,
|
useModalStore,
|
||||||
useSearchStore
|
useSearchStore,
|
||||||
|
useUserStore
|
||||||
} from '../../stores';
|
} from '../../stores';
|
||||||
import { friendRequest, userRequest } from '../../api';
|
import { friendRequest, userRequest } from '../../api';
|
||||||
import { DataTableLayout } from '../../components/ui/data-table';
|
import { DataTableLayout } from '../../components/ui/data-table';
|
||||||
@@ -144,7 +146,6 @@
|
|||||||
import { createColumns } from './columns.jsx';
|
import { createColumns } from './columns.jsx';
|
||||||
import { localeIncludes } from '../../shared/utils';
|
import { localeIncludes } from '../../shared/utils';
|
||||||
import removeConfusables, { removeWhitespace } from '../../services/confusables';
|
import removeConfusables, { removeWhitespace } from '../../services/confusables';
|
||||||
import { router } from '../../plugins/router';
|
|
||||||
import { useVrcxVueTable } from '../../lib/table/useVrcxVueTable';
|
import { useVrcxVueTable } from '../../lib/table/useVrcxVueTable';
|
||||||
import { showUserDialog } from '../../coordinators/userCoordinator';
|
import { showUserDialog } from '../../coordinators/userCoordinator';
|
||||||
import { confirmDeleteFriend, handleFriendDelete } from '../../coordinators/friendRelationshipCoordinator';
|
import { confirmDeleteFriend, handleFriendDelete } from '../../coordinators/friendRelationshipCoordinator';
|
||||||
@@ -156,7 +157,10 @@
|
|||||||
|
|
||||||
const { friends, allFavoriteFriendIds } = storeToRefs(useFriendStore());
|
const { friends, allFavoriteFriendIds } = storeToRefs(useFriendStore());
|
||||||
const modalStore = useModalStore();
|
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 appearanceSettingsStore = useAppearanceSettingsStore();
|
||||||
const { randomUserColours } = storeToRefs(appearanceSettingsStore);
|
const { randomUserColours } = storeToRefs(appearanceSettingsStore);
|
||||||
const { userImage } = useUserDisplay();
|
const { userImage } = useUserDisplay();
|
||||||
@@ -303,7 +307,8 @@
|
|||||||
}
|
}
|
||||||
friendStatsRefreshInFlight = Promise.allSettled([
|
friendStatsRefreshInFlight = Promise.allSettled([
|
||||||
getAllUserStats(),
|
getAllUserStats(),
|
||||||
getAllUserMutualCount()
|
getAllUserMutualCount(),
|
||||||
|
getAllUserMutualOptedOut()
|
||||||
]).then((results) => {
|
]).then((results) => {
|
||||||
if (results.every((result) => result.status === 'fulfilled')) {
|
if (results.every((result) => result.status === 'fulfilled')) {
|
||||||
lastFriendStatsRefreshAt = Date.now();
|
lastFriendStatsRefreshAt = Date.now();
|
||||||
@@ -556,8 +561,13 @@
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function openChartsTab() {
|
async function loadMutualFriends() {
|
||||||
router.push({ name: 'charts' });
|
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 {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
AvatarFallback,
|
AvatarFallback,
|
||||||
@@ -391,7 +391,21 @@ export const createColumns = ({
|
|||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const count = row.original?.$mutualCount;
|
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