refactor global search

This commit is contained in:
pa
2026-03-14 19:59:21 +09:00
parent 45f3eacf21
commit b750d3fb9a
20 changed files with 655 additions and 181 deletions

View File

@@ -24,6 +24,7 @@ import { useAvatarProviderStore } from '../stores/avatarProvider';
import { useAvatarStore } from '../stores/avatar';
import { useFavoriteStore } from '../stores/favorite';
import { useModalStore } from '../stores/modal';
import { syncAvatarSearchIndex, removeAvatarSearchIndex } from './searchIndexCoordinator';
import { useUiStore } from '../stores/ui';
import { useUserStore } from '../stores/user';
import { useVRCXUpdaterStore } from '../stores/vrcxUpdater';
@@ -67,6 +68,7 @@ export function applyAvatar(json) {
database.addAvatarToCache(avatarRef);
}
patchAvatarFromEvent(ref);
syncAvatarSearchIndex(ref);
return ref;
}
@@ -632,3 +634,12 @@ export async function preloadOwnAvatars() {
}
});
}
/**
* @param {string} id
*/
export function removeAvatarFromCache(id) {
const avatarStore = useAvatarStore();
avatarStore.cachedAvatars.delete(id);
removeAvatarSearchIndex(id);
}

View File

@@ -7,6 +7,7 @@ import { useFriendStore } from '../stores/friend';
import { useGeneralSettingsStore } from '../stores/settings/general';
import { useUserStore } from '../stores/user';
import { useWorldStore } from '../stores/world';
import { rebuildFavoriteSearchIndex } from './searchIndexCoordinator';
import { applyWorld } from './worldCoordinator';
import { runUpdateFriendFlow } from './friendPresenceCoordinator';
import { avatarRequest, favoriteRequest, queryRequest } from '../api';
@@ -134,6 +135,7 @@ export function handleFavoriteAtDelete(ref) {
avatarStore.setAvatarDialogIsFavorite(false);
}
favoriteStore.countFavoriteGroups();
rebuildFavoriteSearchIndex();
}
/**
@@ -374,6 +376,7 @@ export async function applyFavorite(type, objectId) {
}
}
}
rebuildFavoriteSearchIndex();
}
// --- Refresh flows ---
@@ -1284,5 +1287,8 @@ export function onLoginStateChanged(isLoggedIn) {
friendStore.localFavoriteFriends.clear();
if (isLoggedIn) {
initFavorites();
} else {
rebuildFavoriteSearchIndex();
}
}

View File

@@ -4,6 +4,7 @@ import { database } from '../services/database';
import { useFeedStore } from '../stores/feed';
import { useFriendStore } from '../stores/friend';
import { useNotificationStore } from '../stores/notification';
import { syncFriendSearchIndex } from './searchIndexCoordinator';
import { useSharedFeedStore } from '../stores/sharedFeed';
import { useUserStore } from '../stores/user';
import { userRequest } from '../api';
@@ -117,6 +118,7 @@ export async function runUpdateFriendDelayedCheckFlow(
}
if (ref?.displayName) {
ctx.name = ref.displayName;
syncFriendSearchIndex(ctx);
}
ctx.isVIP = isVIP;
}
@@ -205,6 +207,7 @@ export async function runUpdateFriendFlow(
}
if (typeof ref !== 'undefined' && ctx.name !== ref.displayName) {
ctx.name = ref.displayName;
syncFriendSearchIndex(ctx);
}
return;
}
@@ -216,6 +219,7 @@ export async function runUpdateFriendFlow(
ctx.isVIP = isVIP;
if (typeof ref !== 'undefined') {
ctx.name = ref.displayName;
syncFriendSearchIndex(ctx);
}
if (!watchState.isFriendsLoaded) {
await runUpdateFriendDelayedCheckFlow(
@@ -250,6 +254,7 @@ export async function runUpdateFriendFlow(
ctx.isVIP = isVIP;
if (typeof ref !== 'undefined') {
ctx.name = ref.displayName;
syncFriendSearchIndex(ctx);
await runUpdateFriendDelayedCheckFlow(
ctx,
ctx.ref.state,
@@ -304,3 +309,4 @@ export async function runPendingOfflineTickFlow({
}
}
}

View File

@@ -11,6 +11,10 @@ import { useNotificationStore } from '../stores/notification';
import { useSharedFeedStore } from '../stores/sharedFeed';
import { useUiStore } from '../stores/ui';
import { useUserStore } from '../stores/user';
import {
removeFriendSearchIndex,
syncFriendSearchIndex
} from './searchIndexCoordinator';
import { watchState } from '../services/watchState';
import configRepository from '../services/config';
@@ -44,6 +48,7 @@ export function handleFriendDelete(args) {
D.isFriend = false;
runDeleteFriendshipFlow(args.params.userId);
friendStore.deleteFriend(args.params.userId);
removeFriendSearchIndex(args.params.userId);
}
/**
@@ -53,6 +58,10 @@ export function handleFriendAdd(args) {
const friendStore = useFriendStore();
addFriendship(args.params.userId);
friendStore.addFriend(args.params.userId);
const ctx = friendStore.friends.get(args.params.userId);
if (ctx) {
syncFriendSearchIndex(ctx);
}
}
/**
@@ -136,6 +145,10 @@ export function addFriendship(id) {
state.friendNumber
);
friendStore.addFriend(id, ref.state);
const friendCtx = friendStore.friends.get(id);
if (friendCtx) {
syncFriendSearchIndex(friendCtx);
}
const friendLogHistory = {
created_at: new Date().toJSON(),
type: 'Friend',
@@ -316,7 +329,16 @@ export function updateUserCurrentStatus(ref) {
const appearanceSettingsStore = useAppearanceSettingsStore();
if (watchState.isFriendsLoaded) {
friendStore.refreshFriendsStatus(ref);
const { added, removed } = friendStore.refreshFriendsStatus(ref);
for (const id of added) {
const ctx = friendStore.friends.get(id);
if (ctx) {
syncFriendSearchIndex(ctx);
}
}
for (const id of removed) {
removeFriendSearchIndex(id);
}
}
friendStore.updateOnlineFriendCounter();
@@ -383,6 +405,7 @@ export function runDeleteFriendshipFlow(
uiStore.notifyMenu('friend-log');
}
friendStore.deleteFriend(id);
removeFriendSearchIndex(id);
}
});
}

View File

@@ -2,6 +2,7 @@ import { toast } from 'vue-sonner';
import { AppDebug } from '../services/appConfig';
import { migrateMemos } from './memoCoordinator';
import { syncFriendSearchIndex } from './searchIndexCoordinator';
import { reconnectWebSocket } from '../services/websocket';
import { useAuthStore } from '../stores/auth';
import { useFriendStore } from '../stores/friend';
@@ -56,6 +57,11 @@ export async function runInitFriendsListFlow(t) {
}
}
// bulk sync friends to search index after initial load
for (const ctx of friendStore.friends.values()) {
syncFriendSearchIndex(ctx);
}
friendStore.tryApplyFriendOrder(); // once again
friendStore.getAllUserStats(); // joinCount, lastSeen, timeSpent

View File

@@ -18,13 +18,13 @@ import { useNotificationStore } from '../stores/notification';
import { useUiStore } from '../stores/ui';
import { useUserStore } from '../stores/user';
import { useGroupStore } from '../stores/group';
import { syncGroupSearchIndex, removeGroupSearchIndex, clearGroupSearchIndex } from './searchIndexCoordinator';
import { watchState } from '../services/watchState';
import configRepository from '../services/config';
import * as workerTimers from 'worker-timers';
// ─── Internal helpers (not exported) ─────────────────────────────────────────
/**
* @param ref
@@ -48,7 +48,6 @@ function applyGroupLanguage(ref) {
}
}
// ─── Core entity application ─────────────────────────────────────────────────
/**
*
@@ -132,6 +131,9 @@ export function applyGroup(json) {
D.ref = ref;
}
patchGroupFromEvent(ref);
if (groupStore.currentUserGroups.has(ref.id)) {
syncGroupSearchIndex(ref);
}
return ref;
}
@@ -178,7 +180,6 @@ export function applyGroupMember(json) {
return json;
}
// ─── Group change notifications ──────────────────────────────────────────────
/**
*
@@ -274,7 +275,6 @@ function groupRoleChange(ref, oldRoles, newRoles, oldRoleIds, newRoleIds) {
}
}
// ─── Dialog flows ────────────────────────────────────────────────────────────
/**
*
@@ -461,7 +461,6 @@ export function getGroupDialogGroup(groupId, existingRef) {
});
}
// ─── Group lifecycle flows ───────────────────────────────────────────────────
/**
*
@@ -508,6 +507,7 @@ export function onGroupJoined(groupId) {
name: '',
iconUrl: ''
});
syncGroupSearchIndex({ id: groupId, name: '', ownerId: '', iconUrl: '' });
groupRequest.getGroup({ groupId, includeRoles: true }).then((args) => {
applyGroup(args.json);
saveCurrentUserGroups();
@@ -539,6 +539,7 @@ export async function onGroupLeft(groupId) {
}
if (groupStore.currentUserGroups.has(groupId)) {
groupStore.currentUserGroups.delete(groupId);
removeGroupSearchIndex(groupId);
groupChange(ref, 'Left group');
// delay to wait for json to be assigned to ref
@@ -546,7 +547,6 @@ export async function onGroupLeft(groupId) {
}
}
// ─── User group management ───────────────────────────────────────────────────
/**
*
@@ -589,6 +589,7 @@ export async function loadCurrentUserGroups(userId, groups) {
);
groupStore.cachedGroups.clear();
groupStore.currentUserGroups.clear();
clearGroupSearchIndex();
for (const group of savedGroups) {
const json = {
id: group.id,
@@ -602,6 +603,7 @@ export async function loadCurrentUserGroups(userId, groups) {
};
const ref = applyGroup(json);
groupStore.currentUserGroups.set(group.id, ref);
syncGroupSearchIndex(ref);
}
if (groups) {
@@ -620,6 +622,7 @@ export async function loadCurrentUserGroups(userId, groups) {
});
const ref = applyGroup(args.json);
groupStore.currentUserGroups.set(groupId, ref);
syncGroupSearchIndex(ref);
} catch (err) {
console.error(err);
}
@@ -643,10 +646,12 @@ export async function getCurrentUserGroups() {
});
handleGroupList(args);
groupStore.currentUserGroups.clear();
clearGroupSearchIndex();
for (const group of args.json) {
const ref = applyGroup(group);
if (!groupStore.currentUserGroups.has(group.id)) {
groupStore.currentUserGroups.set(group.id, ref);
syncGroupSearchIndex(ref);
}
}
const args1 = await groupRequest.getGroupPermissions({
@@ -704,7 +709,6 @@ export async function updateInGameGroupOrder() {
}
}
// ─── Group actions ───────────────────────────────────────────────────────────
/**
*

View File

@@ -0,0 +1,106 @@
import { watch } from 'vue';
import { useSearchIndexStore } from '../stores/searchIndex';
import { watchState } from '../services/watchState';
/**
* @param {object} ctx
*/
export function syncFriendSearchIndex(ctx) {
useSearchIndexStore().syncFriend(ctx);
}
/**
* @param {string} id
*/
export function removeFriendSearchIndex(id) {
useSearchIndexStore().removeFriend(id);
}
export function clearFriendSearchIndex() {
useSearchIndexStore().clearFriends();
}
/**
* @param {object} ref
*/
export function syncAvatarSearchIndex(ref) {
useSearchIndexStore().upsertAvatar(ref);
}
/**
* @param {string} id
*/
export function removeAvatarSearchIndex(id) {
useSearchIndexStore().removeAvatar(id);
}
export function clearAvatarSearchIndex() {
useSearchIndexStore().clearAvatars();
}
/**
* @param {object} ref
*/
export function syncWorldSearchIndex(ref) {
useSearchIndexStore().upsertWorld(ref);
}
/**
* @param {string} id
*/
export function removeWorldSearchIndex(id) {
useSearchIndexStore().removeWorld(id);
}
export function clearWorldSearchIndex() {
useSearchIndexStore().clearWorlds();
}
/**
* @param {object} ref
*/
export function syncGroupSearchIndex(ref) {
useSearchIndexStore().upsertGroup(ref);
}
/**
* @param {string} id
*/
export function removeGroupSearchIndex(id) {
useSearchIndexStore().removeGroup(id);
}
export function clearGroupSearchIndex() {
useSearchIndexStore().clearGroups();
}
export function rebuildFavoriteSearchIndex() {
useSearchIndexStore().rebuildFavoritesFromStore();
}
export function clearFavoriteSearchIndex() {
useSearchIndexStore().clearFavorites();
}
/**
* Registers a single login-state watcher that clears the entire search index
* on every login/logout transition, so individual stores don't need to.
*/
export function resetSearchIndexOnLogin() {
const searchIndexStore = useSearchIndexStore();
watch(
() => watchState.isLoggedIn,
() => {
searchIndexStore.clearFriends();
searchIndexStore.clearAvatars();
searchIndexStore.clearWorlds();
searchIndexStore.clearGroups();
searchIndexStore.clearFavorites();
},
{ flush: 'sync' }
);
}

View File

@@ -53,6 +53,8 @@ import { useModerationStore } from '../stores/moderation';
import { useNotificationStore } from '../stores/notification';
import { usePhotonStore } from '../stores/photon';
import { useSearchStore } from '../stores/search';
import { syncFriendSearchIndex } from './searchIndexCoordinator';
import { removeAvatarFromCache } from './avatarCoordinator';
import { useSharedFeedStore } from '../stores/sharedFeed';
import { useUiStore } from '../stores/ui';
import { useUserStore } from '../stores/user';
@@ -181,6 +183,7 @@ export function applyUser(json) {
if (friendCtx) {
friendCtx.ref = ref;
friendCtx.name = ref.displayName;
syncFriendSearchIndex(friendCtx);
}
if (ref.id === currentUser.id) {
if (ref.status) {
@@ -306,6 +309,7 @@ export function showUserDialog(userId) {
} else {
ref.$nickName = '';
}
syncFriendSearchIndex(ref);
}
}
});
@@ -583,7 +587,7 @@ export async function refreshUserDialogAvatars(fileId) {
};
for (const ref of avatarStore.cachedAvatars.values()) {
if (ref.authorId === D.id) {
avatarStore.cachedAvatars.delete(ref.id);
removeAvatarFromCache(ref.id);
}
}
const map = new Map();

View File

@@ -1,3 +1,5 @@
import { removeAvatarFromCache } from './avatarCoordinator';
import { removeWorldFromCache } from './worldCoordinator';
import { useAvatarStore } from '../stores/avatar';
import { useFavoriteStore } from '../stores/favorite';
import { useFriendStore } from '../stores/friend';
@@ -9,6 +11,7 @@ import { useUserStore } from '../stores/user';
import { useWorldStore } from '../stores/world';
import { failedGetRequests } from '../services/request';
/**
* Clears caches across multiple stores while preserving data that is
* still needed (friends, current user, favorites, active instances).
@@ -41,7 +44,7 @@ export function clearVRCXCache() {
ref.authorId !== userStore.currentUser.id &&
!favoriteStore.localWorldFavoritesList.includes(id)
) {
worldStore.cachedWorlds.delete(id);
removeWorldFromCache(id);
}
});
avatarStore.cachedAvatars.forEach((ref, id) => {
@@ -51,7 +54,7 @@ export function clearVRCXCache() {
!favoriteStore.localAvatarFavoritesList.includes(id) &&
!avatarStore.avatarHistory.includes(id)
) {
avatarStore.cachedAvatars.delete(id);
removeAvatarFromCache(id);
}
});
groupStore.cachedGroups.forEach((ref, id) => {

View File

@@ -20,6 +20,7 @@ import { applyFavorite } from './favoriteCoordinator';
import { useFavoriteStore } from '../stores/favorite';
import { useInstanceStore } from '../stores/instance';
import { useLocationStore } from '../stores/location';
import { syncWorldSearchIndex, removeWorldSearchIndex } from './searchIndexCoordinator';
import { useUiStore } from '../stores/ui';
import { useUserStore } from '../stores/user';
import { useWorldStore } from '../stores/world';
@@ -221,6 +222,7 @@ export function applyWorld(json) {
database.addWorldToCache(ref);
}
patchWorldFromEvent(ref);
syncWorldSearchIndex(ref);
return ref;
}
@@ -247,3 +249,12 @@ export async function preloadOwnWorlds() {
}
});
}
/**
* @param {string} id
*/
export function removeWorldFromCache(id) {
const worldStore = useWorldStore();
worldStore.cachedWorlds.delete(id);
removeWorldSearchIndex(id);
}