refactor friends sort

This commit is contained in:
pa
2026-03-14 20:57:23 +09:00
parent 84f46a5645
commit a8d1b7a905
8 changed files with 477 additions and 193 deletions
@@ -138,6 +138,29 @@
params: {} params: {}
}); });
const friendSections = computed(() => [
{
key: 'friendsInInstance',
label: t('dialog.invite.friends_in_instance'),
friends: props.inviteDialog?.friendsInInstance ?? []
},
{
key: 'vip',
label: t('side_panel.favorite'),
friends: vipFriends.value
},
{
key: 'online',
label: t('side_panel.online'),
friends: onlineFriends.value
},
{
key: 'active',
label: t('side_panel.active'),
friends: activeFriends.value
}
]);
const userPickerGroups = computed(() => { const userPickerGroups = computed(() => {
const groups = []; const groups = [];
@@ -156,7 +179,7 @@
}); });
} }
const addFriendGroup = (key, label, friends) => { const addFriendGroup = ({ key, label, friends }) => {
if (!friends?.length) return; if (!friends?.length) return;
groups.push({ groups.push({
key, key,
@@ -174,10 +197,7 @@
}); });
}; };
addFriendGroup('friendsInInstance', t('dialog.invite.friends_in_instance'), props.inviteDialog?.friendsInInstance); friendSections.value.forEach(addFriendGroup);
addFriendGroup('vip', t('side_panel.favorite'), vipFriends.value);
addFriendGroup('online', t('side_panel.online'), onlineFriends.value);
addFriendGroup('active', t('side_panel.active'), activeFriends.value);
return groups; return groups;
}); });
@@ -194,10 +214,11 @@
const friendById = computed(() => { const friendById = computed(() => {
const map = new Map(); const map = new Map();
for (const friend of props.inviteDialog?.friendsInInstance ?? []) map.set(friend.id, friend); for (const section of friendSections.value) {
for (const friend of vipFriends.value) map.set(friend.id, friend); for (const friend of section.friends ?? []) {
for (const friend of onlineFriends.value) map.set(friend.id, friend); map.set(friend.id, friend);
for (const friend of activeFriends.value) map.set(friend.id, friend); }
}
return map; return map;
}); });
+30 -9
View File
@@ -142,12 +142,36 @@
} }
]); ]);
const friendSections = computed(() => [
{
key: 'vip',
label: t('side_panel.favorite'),
friends: vipFriends.value
},
{
key: 'online',
label: t('side_panel.online'),
friends: onlineFriends.value
},
{
key: 'active',
label: t('side_panel.active'),
friends: activeFriends.value
},
{
key: 'offline',
label: t('side_panel.offline'),
friends: offlineFriends.value
}
]);
const friendById = computed(() => { const friendById = computed(() => {
const map = new Map(); const map = new Map();
for (const friend of vipFriends.value) map.set(friend.id, friend); for (const section of friendSections.value) {
for (const friend of onlineFriends.value) map.set(friend.id, friend); for (const friend of section.friends ?? []) {
for (const friend of activeFriends.value) map.set(friend.id, friend); map.set(friend.id, friend);
for (const friend of offlineFriends.value) map.set(friend.id, friend); }
}
return map; return map;
}); });
@@ -190,7 +214,7 @@
}); });
} }
const addFriendGroup = (key, label, friends) => { const addFriendGroup = ({ key, label, friends }) => {
if (!friends?.length) return; if (!friends?.length) return;
groups.push({ groups.push({
key, key,
@@ -208,10 +232,7 @@
}); });
}; };
addFriendGroup('vip', t('side_panel.favorite'), vipFriends.value); friendSections.value.forEach(addFriendGroup);
addFriendGroup('online', t('side_panel.online'), onlineFriends.value);
addFriendGroup('active', t('side_panel.active'), activeFriends.value);
addFriendGroup('offline', t('side_panel.offline'), offlineFriends.value);
return groups; return groups;
}); });
@@ -675,12 +675,36 @@
return names.slice(0, 3).join(', ') + (names.length > 3 ? ` +${names.length - 3}` : ''); return names.slice(0, 3).join(', ') + (names.length > 3 ? ` +${names.length - 3}` : '');
}); });
const friendSections = computed(() => [
{
key: 'vip',
label: t('side_panel.favorite'),
friends: vipFriends.value
},
{
key: 'online',
label: t('side_panel.online'),
friends: onlineFriends.value
},
{
key: 'active',
label: t('side_panel.active'),
friends: activeFriends.value
},
{
key: 'offline',
label: t('side_panel.offline'),
friends: offlineFriends.value
}
]);
const friendById = computed(() => { const friendById = computed(() => {
const map = new Map(); const map = new Map();
for (const friend of vipFriends.value) map.set(friend.id, friend); for (const section of friendSections.value) {
for (const friend of onlineFriends.value) map.set(friend.id, friend); for (const friend of section.friends ?? []) {
for (const friend of activeFriends.value) map.set(friend.id, friend); map.set(friend.id, friend);
for (const friend of offlineFriends.value) map.set(friend.id, friend); }
}
return map; return map;
}); });
@@ -714,7 +738,7 @@
}); });
} }
const addFriendGroup = (key, label, friends) => { const addFriendGroup = ({ key, label, friends }) => {
if (!friends?.length) return; if (!friends?.length) return;
groups.push({ groups.push({
key, key,
@@ -732,10 +756,7 @@
}); });
}; };
addFriendGroup('vip', t('side_panel.favorite'), vipFriends.value); friendSections.value.forEach(addFriendGroup);
addFriendGroup('online', t('side_panel.online'), onlineFriends.value);
addFriendGroup('active', t('side_panel.active'), activeFriends.value);
addFriendGroup('offline', t('side_panel.offline'), offlineFriends.value);
return groups; return groups;
}); });
@@ -121,6 +121,7 @@ export async function runUpdateFriendDelayedCheckFlow(
syncFriendSearchIndex(ctx); syncFriendSearchIndex(ctx);
} }
ctx.isVIP = isVIP; ctx.isVIP = isVIP;
friendStore.reindexSortedFriend(ctx);
} }
/** /**
@@ -209,6 +210,7 @@ export async function runUpdateFriendFlow(
ctx.name = ref.displayName; ctx.name = ref.displayName;
syncFriendSearchIndex(ctx); syncFriendSearchIndex(ctx);
} }
friendStore.reindexSortedFriend(ctx);
return; return;
} }
if ( if (
@@ -248,6 +250,7 @@ export async function runUpdateFriendFlow(
previousLocationAt: $location_at previousLocationAt: $location_at
}); });
ctx.pendingOffline = true; ctx.pendingOffline = true;
friendStore.reindexSortedFriend(ctx);
return; return;
} }
ctx.ref = ref; ctx.ref = ref;
@@ -262,6 +265,8 @@ export async function runUpdateFriendFlow(
$location_at, $location_at,
{ now, nowIso } { now, nowIso }
); );
} else {
friendStore.reindexSortedFriend(ctx);
} }
} }
@@ -309,4 +314,3 @@ export async function runPendingOfflineTickFlow({
} }
} }
} }
+1
View File
@@ -184,6 +184,7 @@ export function applyUser(json) {
friendCtx.ref = ref; friendCtx.ref = ref;
friendCtx.name = ref.displayName; friendCtx.name = ref.displayName;
syncFriendSearchIndex(friendCtx); syncFriendSearchIndex(friendCtx);
friendStore.reindexSortedFriend(friendCtx);
} }
if (ref.id === currentUser.id) { if (ref.id === currentUser.id) {
if (ref.status) { if (ref.status) {
+256 -46
View File
@@ -1,4 +1,4 @@
import { computed, reactive, ref, watch } from 'vue'; import { computed, reactive, ref, shallowRef, watch } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@@ -56,6 +56,63 @@ export const useFriendStore = defineStore('Friend', () => {
const friends = reactive(new Map()); const friends = reactive(new Map());
const localFavoriteFriends = reactive(new Set()); const localFavoriteFriends = reactive(new Set());
const sortedFriends = shallowRef([]);
let sortedFriendsBatchDepth = 0;
let pendingSortedFriendsRebuild = false;
const derivedDebugCounters = reactive({
allFavoriteFriendIds: 0,
allFavoriteOnlineFriends: 0,
vipFriends: 0,
onlineFriends: 0,
activeFriends: 0,
offlineFriends: 0,
friendsInSameInstance: 0
});
/**
* Tracks recomputes for the hottest friend-derived lists.
* Guarded by AppDebug.debugFriendState so normal behavior stays unchanged.
* @param {keyof typeof derivedDebugCounters} name
* @param {number} resultSize
*/
function trackDerivedDebug(name, resultSize) {
derivedDebugCounters[name] += 1;
if (!AppDebug.debugFriendState) {
return;
}
console.log('[friendStore derived]', {
name,
count: derivedDebugCounters[name],
resultSize,
friendCount: friends.size,
sortMethods: appearanceSettingsStore.sidebarSortMethods
});
}
/**
*
*/
function resetDerivedDebugCounters() {
for (const key in derivedDebugCounters) {
derivedDebugCounters[key] = 0;
}
if (AppDebug.debugFriendState) {
console.log('[friendStore derived] counters reset');
}
}
/**
*
* @returns {Record<string, number>}
*/
function getDerivedDebugCounters() {
const snapshot = { ...derivedDebugCounters };
if (AppDebug.debugFriendState) {
console.log('[friendStore derived] counters snapshot', snapshot);
}
return snapshot;
}
const allFavoriteFriendIds = computed(() => { const allFavoriteFriendIds = computed(() => {
const favoriteStore = useFavoriteStore(); const favoriteStore = useFavoriteStore();
@@ -73,20 +130,131 @@ export const useFriendStore = defineStore('Friend', () => {
} }
} }
} }
trackDerivedDebug('allFavoriteFriendIds', set.size);
return set; return set;
}); });
const allFavoriteOnlineFriends = computed(() => { /**
return Array.from(friends.values()) *
.filter( * @returns {(a: object, b: object) => number}
(f) => */
f.state === 'online' && allFavoriteFriendIds.value.has(f.id) function getSortedFriendsComparator() {
) return getFriendsSortFunction(appearanceSettingsStore.sidebarSortMethods);
.sort( }
getFriendsSortFunction(
appearanceSettingsStore.sidebarSortMethods /**
) *
* @param {string} id
* @returns {number}
*/
function findSortedFriendIndex(id) {
return sortedFriends.value.findIndex((friend) => friend.id === id);
}
/**
*
*/
function rebuildSortedFriends() {
sortedFriends.value = Array.from(friends.values()).sort(
getSortedFriendsComparator()
); );
pendingSortedFriendsRebuild = false;
}
/**
*
*/
function beginSortedFriendsBatch() {
sortedFriendsBatchDepth += 1;
}
/**
*
*/
function endSortedFriendsBatch() {
if (sortedFriendsBatchDepth === 0) {
return;
}
sortedFriendsBatchDepth -= 1;
if (sortedFriendsBatchDepth === 0 && pendingSortedFriendsRebuild) {
rebuildSortedFriends();
}
}
/**
*
* @template T
* @param {() => T} fn
* @returns {T}
*/
function runInSortedFriendsBatch(fn) {
beginSortedFriendsBatch();
try {
return fn();
} finally {
endSortedFriendsBatch();
}
}
/**
*
* @param {string} id
*/
function removeSortedFriend(id) {
if (sortedFriendsBatchDepth > 0) {
pendingSortedFriendsRebuild = true;
return;
}
const index = findSortedFriendIndex(id);
if (index === -1) {
return;
}
const next = sortedFriends.value.slice();
next.splice(index, 1);
sortedFriends.value = next;
}
/**
*
* @param {object | string} input
*/
function reindexSortedFriend(input) {
const ctx =
typeof input === 'string' ? friends.get(input) : input;
if (!ctx) {
return;
}
if (sortedFriendsBatchDepth > 0) {
pendingSortedFriendsRebuild = true;
return;
}
const compare = getSortedFriendsComparator();
const next = sortedFriends.value.slice();
const existingIndex = next.findIndex((friend) => friend.id === ctx.id);
if (existingIndex !== -1) {
next.splice(existingIndex, 1);
}
let low = 0;
let high = next.length;
while (low < high) {
const mid = Math.floor((low + high) / 2);
if (compare(next[mid], ctx) <= 0) {
low = mid + 1;
} else {
high = mid;
}
}
next.splice(low, 0, ctx);
sortedFriends.value = next;
}
const allFavoriteOnlineFriends = computed(() => {
const favoriteIds = allFavoriteFriendIds.value;
const result = sortedFriends.value.filter(
(f) => f.state === 'online' && favoriteIds.has(f.id)
);
trackDerivedDebug('allFavoriteOnlineFriends', result.length);
return result;
}); });
const isRefreshFriendsLoading = ref(false); const isRefreshFriendsLoading = ref(false);
@@ -131,50 +299,42 @@ export const useFriendStore = defineStore('Friend', () => {
); );
const vipFriends = computed(() => { const vipFriends = computed(() => {
return Array.from(friends.values()) const result = sortedFriends.value.filter(
.filter((f) => f.state === 'online' && f.isVIP) (f) => f.state === 'online' && f.isVIP
.sort(
getFriendsSortFunction(
appearanceSettingsStore.sidebarSortMethods
)
); );
trackDerivedDebug('vipFriends', result.length);
return result;
}); });
const onlineFriends = computed(() => { const onlineFriends = computed(() => {
return Array.from(friends.values()) const result = sortedFriends.value.filter(
.filter((f) => f.state === 'online' && !f.isVIP) (f) => f.state === 'online' && !f.isVIP
.sort(
getFriendsSortFunction(
appearanceSettingsStore.sidebarSortMethods
)
); );
trackDerivedDebug('onlineFriends', result.length);
return result;
}); });
const activeFriends = computed(() => { const activeFriends = computed(() => {
return Array.from(friends.values()) const result = sortedFriends.value.filter((f) => f.state === 'active');
.filter((f) => f.state === 'active') trackDerivedDebug('activeFriends', result.length);
.sort( return result;
getFriendsSortFunction(
appearanceSettingsStore.sidebarSortMethods
)
);
}); });
const offlineFriends = computed(() => { const offlineFriends = computed(() => {
return Array.from(friends.values()) const result = sortedFriends.value.filter(
.filter((f) => f.state === 'offline' || !f.state) (f) => f.state === 'offline' || !f.state
.sort(
getFriendsSortFunction(
appearanceSettingsStore.sidebarSortMethods
)
); );
trackDerivedDebug('offlineFriends', result.length);
return result;
}); });
const friendsInSameInstance = computed(() => { const friendsInSameInstance = computed(() => {
const friendsList = {}; const friendsList = {};
const allFriends = [...vipFriends.value, ...onlineFriends.value]; sortedFriends.value.forEach((friend) => {
allFriends.forEach((friend) => { if (friend.state !== 'online') {
return;
}
if (!friend.ref?.$location) { if (!friend.ref?.$location) {
return; return;
} }
@@ -200,23 +360,22 @@ export const useFriendStore = defineStore('Friend', () => {
const sortedFriendsList = []; const sortedFriendsList = [];
for (const group of Object.values(friendsList)) { for (const group of Object.values(friendsList)) {
if (group.length > 1) { if (group.length > 1) {
sortedFriendsList.push( // Group order already matches the globally sorted online list.
group.sort( sortedFriendsList.push(group);
getFriendsSortFunction(
appearanceSettingsStore.sidebarSortMethods
)
)
);
} }
} }
return sortedFriendsList.sort((a, b) => b.length - a.length); const result = sortedFriendsList.sort((a, b) => b.length - a.length);
trackDerivedDebug('friendsInSameInstance', result.length);
return result;
}); });
watch( watch(
() => watchState.isLoggedIn, () => watchState.isLoggedIn,
(isLoggedIn) => { (isLoggedIn) => {
friends.clear(); friends.clear();
sortedFriends.value = [];
pendingSortedFriendsRebuild = false;
state.friendNumber = 0; state.friendNumber = 0;
friendLog.clear(); friendLog.clear();
friendLogTable.value.data = []; friendLogTable.value.data = [];
@@ -236,6 +395,14 @@ export const useFriendStore = defineStore('Friend', () => {
{ flush: 'sync' } { flush: 'sync' }
); );
watch(
() => appearanceSettingsStore.sidebarSortMethods,
() => {
rebuildSortedFriends();
},
{ deep: true }
);
watch( watch(
() => watchState.isFriendsLoaded, () => watchState.isFriendsLoaded,
(isFriendsLoaded) => { (isFriendsLoaded) => {
@@ -293,13 +460,16 @@ export const useFriendStore = defineStore('Friend', () => {
* *
*/ */
function updateSidebarFavorites() { function updateSidebarFavorites() {
runInSortedFriendsBatch(() => {
for (const ctx of friends.values()) { for (const ctx of friends.values()) {
const isVIP = localFavoriteFriends.has(ctx.id); const isVIP = localFavoriteFriends.has(ctx.id);
if (ctx.isVIP === isVIP) { if (ctx.isVIP === isVIP) {
continue; continue;
} }
ctx.isVIP = isVIP; ctx.isVIP = isVIP;
reindexSortedFriend(ctx);
} }
});
} }
/** /**
@@ -320,6 +490,7 @@ export const useFriendStore = defineStore('Friend', () => {
return; return;
} }
friends.delete(id); friends.delete(id);
removeSortedFriend(id);
} }
/** /**
@@ -327,6 +498,7 @@ export const useFriendStore = defineStore('Friend', () => {
* @param ref * @param ref
*/ */
function refreshFriendsStatus(ref) { function refreshFriendsStatus(ref) {
return runInSortedFriendsBatch(() => {
let id; let id;
const map = new Map(); const map = new Map();
for (id of ref.friends) { for (id of ref.friends) {
@@ -359,6 +531,7 @@ export const useFriendStore = defineStore('Friend', () => {
} }
} }
return { added, removed }; return { added, removed };
});
} }
/** /**
@@ -408,6 +581,29 @@ export const useFriendStore = defineStore('Friend', () => {
ctx.name = ref.name; ctx.name = ref.name;
} }
friends.set(id, ctx); friends.set(id, ctx);
watchState.isLoggedIn = true
// Startup fill flow:
//
// login
// -> runInitFriendsListFlow()
// -> initFriendLog() / getFriendLog()
// -> refreshFriendsStatus(currentUser)
// -> addFriend(...)
// -> friends.set(id, ctx)
// -> reindexSortedFriend(ctx)
//
// During batch init, reindexSortedFriend() only marks the list dirty.
// When the batch ends:
// -> rebuildSortedFriends()
// -> sortedFriends = sorted(Array.from(friends.values()))
//
// After full friend payloads arrive:
// -> applyUser(friend)
// -> update ctx.ref / ctx.name
// -> reindexSortedFriend(ctx)
// -> batch end
// -> rebuildSortedFriends()
reindexSortedFriend(ctx);
} }
/** /**
@@ -672,14 +868,17 @@ export const useFriendStore = defineStore('Friend', () => {
friend.displayName = item.displayName; friend.displayName = item.displayName;
friendListMap.set(item.userId, friend); friendListMap.set(item.userId, friend);
} }
runInSortedFriendsBatch(() => {
for (item of friendListMap.values()) { for (item of friendListMap.values()) {
ref = friends.get(item.userId); ref = friends.get(item.userId);
if (ref?.ref) { if (ref?.ref) {
ref.ref.$joinCount = item.joinCount; ref.ref.$joinCount = item.joinCount;
ref.ref.$lastSeen = item.lastSeen; ref.ref.$lastSeen = item.lastSeen;
ref.ref.$timeSpent = item.timeSpent; ref.ref.$timeSpent = item.timeSpent;
reindexSortedFriend(ref);
} }
} }
});
} }
/** /**
@@ -687,12 +886,15 @@ export const useFriendStore = defineStore('Friend', () => {
*/ */
async function getAllUserMutualCount() { async function getAllUserMutualCount() {
const mutualCountMap = await database.getMutualCountForAllUsers(); const mutualCountMap = await database.getMutualCountForAllUsers();
runInSortedFriendsBatch(() => {
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) {
ref.ref.$mutualCount = mutualCount; ref.ref.$mutualCount = mutualCount;
reindexSortedFriend(ref);
} }
} }
});
} }
/** /**
@@ -714,6 +916,7 @@ export const useFriendStore = defineStore('Friend', () => {
refreshFriendsStatus(currentUser); refreshFriendsStatus(currentUser);
const sqlValues = []; const sqlValues = [];
const friends = await refreshFriends(); const friends = await refreshFriends();
runInSortedFriendsBatch(() => {
for (const friend of friends) { for (const friend of friends) {
const ref = applyUser(friend); const ref = applyUser(friend);
const row = { const row = {
@@ -725,6 +928,7 @@ export const useFriendStore = defineStore('Friend', () => {
friendLog.set(friend.id, row); friendLog.set(friend.id, row);
sqlValues.unshift(row); sqlValues.unshift(row);
} }
});
database.setFriendLogCurrentArray(sqlValues); database.setFriendLogCurrentArray(sqlValues);
await configRepository.setBool(`friendLogInit_${currentUser.id}`, true); await configRepository.setBool(`friendLogInit_${currentUser.id}`, true);
watchState.isFriendsLoaded = true; watchState.isFriendsLoaded = true;
@@ -808,6 +1012,7 @@ export const useFriendStore = defineStore('Friend', () => {
const friendRef = friends.get(userId); const friendRef = friends.get(userId);
if (friendRef?.ref) { if (friendRef?.ref) {
friendRef.ref.$friendNumber = friendNumber; friendRef.ref.$friendNumber = friendNumber;
reindexSortedFriend(friendRef);
} }
} }
@@ -830,11 +1035,13 @@ export const useFriendStore = defineStore('Friend', () => {
} }
const friendOrder = userStore.currentUser.friends; const friendOrder = userStore.currentUser.friends;
runInSortedFriendsBatch(() => {
for (let i = 0; i < friendOrder.length; i++) { for (let i = 0; i < friendOrder.length; i++) {
const userId = friendOrder[i]; const userId = friendOrder[i];
state.friendNumber++; state.friendNumber++;
setFriendNumber(state.friendNumber, userId); setFriendNumber(state.friendNumber, userId);
} }
});
if (state.friendNumber === 0) { if (state.friendNumber === 0) {
state.friendNumber = friends.size; state.friendNumber = friends.size;
} }
@@ -1179,6 +1386,9 @@ export const useFriendStore = defineStore('Friend', () => {
getFriendLog, getFriendLog,
tryApplyFriendOrder, tryApplyFriendOrder,
resetFriendLog, resetFriendLog,
reindexSortedFriend,
resetDerivedDebugCounters,
getDerivedDebugCounters,
initFriendLogHistoryTable, initFriendLogHistoryTable,
setIsRefreshFriendsLoading setIsRefreshFriendsLoading
}; };
+23 -14
View File
@@ -371,10 +371,16 @@
return ids; return ids;
}); });
const vipFriendsByGroupStatus = computed(() => { const visibleFavoriteOnlineFriends = computed(() => {
const selectedGroups = sidebarFavoriteGroups.value; const selectedGroups = sidebarFavoriteGroups.value;
if (selectedGroups.length === 0) return allFavoriteOnlineFriends.value; if (selectedGroups.length === 0) {
return allFavoriteOnlineFriends.value.filter((f) => displayedVipIds.value.has(f.id)); return allFavoriteOnlineFriends.value;
}
return allFavoriteOnlineFriends.value.filter((friend) => displayedVipIds.value.has(friend.id));
});
const vipFriendsByGroupStatus = computed(() => {
return visibleFavoriteOnlineFriends.value;
}); });
const onlineFriendsByGroupStatus = computed(() => { const onlineFriendsByGroupStatus = computed(() => {
@@ -382,10 +388,11 @@
if (selectedGroups.length === 0) { if (selectedGroups.length === 0) {
return onlineFriends.value.filter((f) => !allFavoriteFriendIds.value.has(f.id)); return onlineFriends.value.filter((f) => !allFavoriteFriendIds.value.has(f.id));
} }
const nonFavOnline = onlineFriends.value.filter((f) => !displayedVipIds.value.has(f.id)); const selectedIds = displayedVipIds.value;
const nonFavOnline = onlineFriends.value.filter((f) => !selectedIds.has(f.id));
const existingIds = new Set(nonFavOnline.map((f) => f.id)); const existingIds = new Set(nonFavOnline.map((f) => f.id));
const unselectedGroupFriends = allFavoriteOnlineFriends.value.filter( const unselectedGroupFriends = allFavoriteOnlineFriends.value.filter(
(f) => !displayedVipIds.value.has(f.id) && !existingIds.has(f.id) (f) => !selectedIds.has(f.id) && !existingIds.has(f.id)
); );
return [...nonFavOnline, ...unselectedGroupFriends].sort(getFriendsSortFunction(sidebarSortMethods.value)); return [...nonFavOnline, ...unselectedGroupFriends].sort(getFriendsSortFunction(sidebarSortMethods.value));
}); });
@@ -415,7 +422,7 @@
const result = []; const result = [];
for (const { key, groupName, memberIds } of groups) { for (const { key, groupName, memberIds } of groups) {
const filteredFriends = allFavoriteOnlineFriends.value.filter((friend) => memberIds.has(friend.id)); const filteredFriends = visibleFavoriteOnlineFriends.value.filter((friend) => memberIds.has(friend.id));
if (filteredFriends.length > 0) { if (filteredFriends.length > 0) {
result.push({ key, groupName, friends: filteredFriends }); result.push({ key, groupName, friends: filteredFriends });
} }
@@ -432,6 +439,15 @@
}); });
}); });
const searchableEntries = computed(() =>
uniqueEntries([
...toEntries(allFavoriteOnlineFriends.value),
...toEntries(onlineFriends.value),
...toEntries(activeFriends.value),
...toEntries(offlineFriends.value)
])
);
/** /**
* *
* @param groupKey * @param groupKey
@@ -446,14 +462,7 @@
const filteredFriends = computed(() => { const filteredFriends = computed(() => {
if (normalizedSearchTerm.value) { if (normalizedSearchTerm.value) {
const pools = [ return searchableEntries.value.filter(({ friend }) => {
...toEntries(allFavoriteOnlineFriends.value),
...toEntries(onlineFriends.value),
...toEntries(activeFriends.value),
...toEntries(offlineFriends.value)
];
return uniqueEntries(pools).filter(({ friend }) => {
const haystack = const haystack =
`${friend.displayName ?? friend.name ?? ''} ${friend.signature ?? ''} ${friend.worldName ?? ''}`.toLowerCase(); `${friend.displayName ?? friend.name ?? ''} ${friend.signature ?? ''} ${friend.worldName ?? ''}`.toLowerCase();
return haystack.includes(normalizedSearchTerm.value); return haystack.includes(normalizedSearchTerm.value);
+35 -38
View File
@@ -275,6 +275,36 @@
const shouldHideSameInstance = computed(() => isSidebarGroupByInstance.value && isHideFriendsInSameInstance.value); const shouldHideSameInstance = computed(() => isSidebarGroupByInstance.value && isHideFriendsInSameInstance.value);
const selectedFavoriteGroupIds = computed(() => {
const selectedGroups = sidebarFavoriteGroups.value;
const hasFilter = selectedGroups.length > 0;
if (!hasFilter) {
return allFavoriteFriendIds.value;
}
const ids = new Set();
const remoteFriendsByGroup = groupedByGroupKeyFavoriteFriends.value;
for (const key of selectedGroups) {
if (key.startsWith('local:')) {
const groupName = key.slice(6);
const userIds = localFriendFavorites.value?.[groupName];
if (userIds) {
for (const id of userIds) ids.add(id);
}
} else if (remoteFriendsByGroup[key]) {
for (const friend of remoteFriendsByGroup[key]) ids.add(friend.id);
}
}
return ids;
});
const visibleFavoriteOnlineFriends = computed(() => {
const filtered = allFavoriteOnlineFriends.value.filter((friend) =>
selectedFavoriteGroupIds.value.has(friend.id)
);
return excludeSameInstance(filtered);
});
/** /**
* *
* @param list * @param list
@@ -293,23 +323,11 @@
return excludeSameInstance(onlineFriends.value.filter((f) => !allFavoriteFriendIds.value.has(f.id))); return excludeSameInstance(onlineFriends.value.filter((f) => !allFavoriteFriendIds.value.has(f.id)));
} }
// When group filter is active, friends in unselected groups should appear in the online list // When group filter is active, friends in unselected groups should appear in the online list
const displayedVipIds = new Set(); const selectedIds = selectedFavoriteGroupIds.value;
const remoteFriendsByGroup = groupedByGroupKeyFavoriteFriends.value; const nonFavOnline = onlineFriends.value.filter((f) => !selectedIds.has(f.id));
for (const key of selectedGroups) {
if (key.startsWith('local:')) {
const groupName = key.slice(6);
const userIds = localFriendFavorites.value?.[groupName];
if (userIds) {
for (const id of userIds) displayedVipIds.add(id);
}
} else if (remoteFriendsByGroup[key]) {
for (const f of remoteFriendsByGroup[key]) displayedVipIds.add(f.id);
}
}
const nonFavOnline = onlineFriends.value.filter((f) => !displayedVipIds.has(f.id));
const existingIds = new Set(nonFavOnline.map((f) => f.id)); const existingIds = new Set(nonFavOnline.map((f) => f.id));
const unselectedGroupFriends = allFavoriteOnlineFriends.value.filter( const unselectedGroupFriends = allFavoriteOnlineFriends.value.filter(
(f) => !displayedVipIds.has(f.id) && !existingIds.has(f.id) (f) => !selectedIds.has(f.id) && !existingIds.has(f.id)
); );
return excludeSameInstance( return excludeSameInstance(
[...nonFavOnline, ...unselectedGroupFriends].sort(getFriendsSortFunction(sidebarSortMethods.value)) [...nonFavOnline, ...unselectedGroupFriends].sort(getFriendsSortFunction(sidebarSortMethods.value))
@@ -317,26 +335,7 @@
}); });
const vipFriendsByGroupStatus = computed(() => { const vipFriendsByGroupStatus = computed(() => {
const selectedGroups = sidebarFavoriteGroups.value; return visibleFavoriteOnlineFriends.value;
const hasFilter = selectedGroups.length > 0;
if (!hasFilter) {
return excludeSameInstance(allFavoriteOnlineFriends.value);
}
// Filter to only include VIP friends whose group key is in selectedGroups
const allowedIds = new Set();
const remoteFriendsByGroup = groupedByGroupKeyFavoriteFriends.value;
for (const key of selectedGroups) {
if (key.startsWith('local:')) {
const groupName = key.slice(6);
const userIds = localFriendFavorites.value?.[groupName];
if (userIds) {
for (const id of userIds) allowedIds.add(id);
}
} else if (remoteFriendsByGroup[key]) {
for (const f of remoteFriendsByGroup[key]) allowedIds.add(f.id);
}
}
return excludeSameInstance(allFavoriteOnlineFriends.value.filter((f) => allowedIds.has(f.id)));
}); });
// VIP friends divide by group // VIP friends divide by group
@@ -369,9 +368,7 @@
// Filter vipFriends per group, preserving vipFriends sort order // Filter vipFriends per group, preserving vipFriends sort order
const result = []; const result = [];
for (const { key, groupName, memberIds } of groups) { for (const { key, groupName, memberIds } of groups) {
const filteredFriends = excludeSameInstance( const filteredFriends = visibleFavoriteOnlineFriends.value.filter((friend) => memberIds.has(friend.id));
allFavoriteOnlineFriends.value.filter((friend) => memberIds.has(friend.id))
);
if (filteredFriends.length > 0) { if (filteredFriends.length > 0) {
result.push(filteredFriends.map((item) => ({ groupName, key, ...item }))); result.push(filteredFriends.map((item) => ({ groupName, key, ...item })));
} }