mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-03 05:26:05 +02:00
feat: add quick search
This commit is contained in:
+87
-4
@@ -15,6 +15,7 @@ import {
|
||||
import { avatarRequest, miscRequest } from '../api';
|
||||
import { AppDebug } from '../service/appConfig';
|
||||
import { database } from '../service/database';
|
||||
import { processBulk } from '../service/request';
|
||||
import { useAdvancedSettingsStore } from './settings/advanced';
|
||||
import { useAvatarProviderStore } from './avatarProvider';
|
||||
import { useFavoriteStore } from './favorite';
|
||||
@@ -79,15 +80,16 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
avatarHistory.value = [];
|
||||
if (isLoggedIn) {
|
||||
getAvatarHistory();
|
||||
preloadOwnAvatars();
|
||||
}
|
||||
},
|
||||
{ flush: 'sync' }
|
||||
);
|
||||
|
||||
/**
|
||||
/ * @param {object} json
|
||||
/ * @returns {object} ref
|
||||
*/
|
||||
* @param {object} json
|
||||
* @returns {object} ref
|
||||
*/
|
||||
function applyAvatar(json) {
|
||||
json.name = replaceBioSymbols(json.name);
|
||||
json.description = replaceBioSymbols(json.description);
|
||||
@@ -332,6 +334,9 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
return ref;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function updateVRChatAvatarCache() {
|
||||
const D = avatarDialog.value;
|
||||
if (D.visible) {
|
||||
@@ -398,11 +403,17 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function clearAvatarHistory() {
|
||||
avatarHistory.value = [];
|
||||
database.clearAvatarHistory();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function promptClearAvatarHistory() {
|
||||
modalStore
|
||||
.confirm({
|
||||
@@ -444,6 +455,11 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param type
|
||||
* @param search
|
||||
*/
|
||||
async function lookupAvatars(type, search) {
|
||||
const avatars = new Map();
|
||||
if (type === 'search') {
|
||||
@@ -507,6 +523,11 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
return avatars;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param authorId
|
||||
* @param fileId
|
||||
*/
|
||||
async function lookupAvatarByImageFileId(authorId, fileId) {
|
||||
for (const providerUrl of avatarProviderStore.avatarRemoteDatabaseProviderList) {
|
||||
const avatar = await lookupAvatarByFileId(providerUrl, fileId);
|
||||
@@ -529,6 +550,11 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param providerUrl
|
||||
* @param fileId
|
||||
*/
|
||||
async function lookupAvatarByFileId(providerUrl, fileId) {
|
||||
try {
|
||||
const url = `${providerUrl}?fileId=${encodeURIComponent(fileId)}`;
|
||||
@@ -568,6 +594,11 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param providerUrl
|
||||
* @param authorId
|
||||
*/
|
||||
async function lookupAvatarsByAuthor(providerUrl, authorId) {
|
||||
const avatars = [];
|
||||
if (!providerUrl || !authorId) {
|
||||
@@ -615,6 +646,10 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
return avatars;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
function selectAvatarWithConfirmation(id) {
|
||||
modalStore
|
||||
.confirm({
|
||||
@@ -628,6 +663,10 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
async function selectAvatarWithoutConfirmation(id) {
|
||||
if (userStore.currentUser.currentAvatar === id) {
|
||||
toast.info('Avatar already selected');
|
||||
@@ -642,6 +681,10 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fileId
|
||||
*/
|
||||
function checkAvatarCache(fileId) {
|
||||
let avatarId = '';
|
||||
for (let ref of cachedAvatars.values()) {
|
||||
@@ -652,6 +695,11 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
return avatarId;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fileId
|
||||
* @param ownerUserId
|
||||
*/
|
||||
async function checkAvatarCacheRemote(fileId, ownerUserId) {
|
||||
if (advancedSettingsStore.avatarRemoteDatabase) {
|
||||
try {
|
||||
@@ -673,6 +721,12 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param refUserId
|
||||
* @param ownerUserId
|
||||
* @param currentAvatarImageUrl
|
||||
*/
|
||||
async function showAvatarAuthorDialog(
|
||||
refUserId,
|
||||
ownerUserId,
|
||||
@@ -712,6 +766,10 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param avatarId
|
||||
*/
|
||||
function addAvatarWearTime(avatarId) {
|
||||
if (!userStore.currentUser.$previousAvatarSwapTime || !avatarId) {
|
||||
return;
|
||||
@@ -721,6 +779,30 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
database.addAvatarTimeSpent(avatarId, timeSpent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload all own avatars into cache at startup for global search.
|
||||
*/
|
||||
async function preloadOwnAvatars() {
|
||||
const params = {
|
||||
n: 50,
|
||||
offset: 0,
|
||||
sort: 'updated',
|
||||
order: 'descending',
|
||||
releaseStatus: 'all',
|
||||
user: 'me'
|
||||
};
|
||||
await processBulk({
|
||||
fn: avatarRequest.getAvatars,
|
||||
N: -1,
|
||||
params,
|
||||
handle: (args) => {
|
||||
for (const json of args.json) {
|
||||
applyAvatar(json);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
avatarDialog,
|
||||
avatarHistory,
|
||||
@@ -741,6 +823,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
selectAvatarWithConfirmation,
|
||||
selectAvatarWithoutConfirmation,
|
||||
showAvatarAuthorDialog,
|
||||
addAvatarWearTime
|
||||
addAvatarWearTime,
|
||||
preloadOwnAvatars
|
||||
};
|
||||
});
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import {
|
||||
searchAvatars,
|
||||
searchFavoriteAvatars,
|
||||
searchFavoriteWorlds,
|
||||
searchFriends,
|
||||
searchGroups,
|
||||
searchWorlds
|
||||
} from '../shared/utils/globalSearchUtils';
|
||||
import { useAvatarStore } from './avatar';
|
||||
import { useFavoriteStore } from './favorite';
|
||||
import { useFriendStore } from './friend';
|
||||
import { useGroupStore } from './group';
|
||||
import { useUserStore } from './user';
|
||||
import { useWorldStore } from './world';
|
||||
|
||||
export const useGlobalSearchStore = defineStore('GlobalSearch', () => {
|
||||
const friendStore = useFriendStore();
|
||||
const favoriteStore = useFavoriteStore();
|
||||
const avatarStore = useAvatarStore();
|
||||
const worldStore = useWorldStore();
|
||||
const groupStore = useGroupStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const isOpen = ref(false);
|
||||
const query = ref('');
|
||||
|
||||
const stringComparer = computed(
|
||||
() =>
|
||||
new Intl.Collator(undefined, {
|
||||
usage: 'search',
|
||||
sensitivity: 'base'
|
||||
})
|
||||
);
|
||||
|
||||
// Reset query when dialog closes
|
||||
watch(isOpen, (open) => {
|
||||
if (!open) {
|
||||
query.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
const currentUserId = computed(() => userStore.currentUser?.id);
|
||||
|
||||
const friendResults = computed(() => {
|
||||
if (!query.value || query.value.length < 2) return [];
|
||||
return searchFriends(
|
||||
query.value,
|
||||
friendStore.friends,
|
||||
stringComparer.value
|
||||
);
|
||||
});
|
||||
|
||||
// Own avatars (filter cachedAvatars by authorId)
|
||||
const ownAvatarResults = computed(() => {
|
||||
if (!query.value || query.value.length < 2) return [];
|
||||
return searchAvatars(
|
||||
query.value,
|
||||
avatarStore.cachedAvatars,
|
||||
stringComparer.value,
|
||||
currentUserId.value
|
||||
);
|
||||
});
|
||||
|
||||
// Favorite avatars (from favoriteStore, deduplicated against own)
|
||||
const favoriteAvatarResults = computed(() => {
|
||||
if (!query.value || query.value.length < 2) return [];
|
||||
const favResults = searchFavoriteAvatars(
|
||||
query.value,
|
||||
favoriteStore.favoriteAvatars,
|
||||
stringComparer.value
|
||||
);
|
||||
// Deduplicate: remove items already in ownAvatarResults
|
||||
const ownIds = new Set(ownAvatarResults.value.map((r) => r.id));
|
||||
return favResults.filter((r) => !ownIds.has(r.id));
|
||||
});
|
||||
|
||||
// Own worlds (filter cachedWorlds by authorId)
|
||||
const ownWorldResults = computed(() => {
|
||||
if (!query.value || query.value.length < 2) return [];
|
||||
return searchWorlds(
|
||||
query.value,
|
||||
worldStore.cachedWorlds,
|
||||
stringComparer.value,
|
||||
currentUserId.value
|
||||
);
|
||||
});
|
||||
|
||||
// Favorite worlds (from favoriteStore, deduplicated against own)
|
||||
const favoriteWorldResults = computed(() => {
|
||||
if (!query.value || query.value.length < 2) return [];
|
||||
const favResults = searchFavoriteWorlds(
|
||||
query.value,
|
||||
favoriteStore.favoriteWorlds,
|
||||
stringComparer.value
|
||||
);
|
||||
// Deduplicate: remove items already in ownWorldResults
|
||||
const ownIds = new Set(ownWorldResults.value.map((r) => r.id));
|
||||
return favResults.filter((r) => !ownIds.has(r.id));
|
||||
});
|
||||
|
||||
// Own groups (filter by ownerId === currentUser)
|
||||
const ownGroupResults = computed(() => {
|
||||
if (!query.value || query.value.length < 2) return [];
|
||||
return searchGroups(
|
||||
query.value,
|
||||
groupStore.currentUserGroups,
|
||||
stringComparer.value,
|
||||
currentUserId.value
|
||||
);
|
||||
});
|
||||
|
||||
// Joined groups (all matching groups, deduplicated against own)
|
||||
const joinedGroupResults = computed(() => {
|
||||
if (!query.value || query.value.length < 2) return [];
|
||||
const allResults = searchGroups(
|
||||
query.value,
|
||||
groupStore.currentUserGroups,
|
||||
stringComparer.value
|
||||
);
|
||||
const ownIds = new Set(ownGroupResults.value.map((r) => r.id));
|
||||
return allResults.filter((r) => !ownIds.has(r.id));
|
||||
});
|
||||
|
||||
const hasResults = computed(
|
||||
() =>
|
||||
friendResults.value.length > 0 ||
|
||||
ownAvatarResults.value.length > 0 ||
|
||||
favoriteAvatarResults.value.length > 0 ||
|
||||
ownWorldResults.value.length > 0 ||
|
||||
favoriteWorldResults.value.length > 0 ||
|
||||
ownGroupResults.value.length > 0 ||
|
||||
joinedGroupResults.value.length > 0
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function open() {
|
||||
isOpen.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function close() {
|
||||
isOpen.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{id: string, type: string}} item
|
||||
*/
|
||||
function selectResult(item) {
|
||||
if (!item) return;
|
||||
|
||||
close();
|
||||
|
||||
switch (item.type) {
|
||||
case 'friend':
|
||||
userStore.showUserDialog(item.id);
|
||||
break;
|
||||
case 'avatar':
|
||||
avatarStore.showAvatarDialog(item.id);
|
||||
break;
|
||||
case 'world':
|
||||
worldStore.showWorldDialog(item.id);
|
||||
break;
|
||||
case 'group':
|
||||
groupStore.showGroupDialog(item.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
query,
|
||||
friendResults,
|
||||
ownAvatarResults,
|
||||
favoriteAvatarResults,
|
||||
ownWorldResults,
|
||||
favoriteWorldResults,
|
||||
ownGroupResults,
|
||||
joinedGroupResults,
|
||||
hasResults,
|
||||
|
||||
open,
|
||||
close,
|
||||
selectResult
|
||||
};
|
||||
});
|
||||
+5
-2
@@ -16,6 +16,7 @@ import { useGalleryStore } from './gallery';
|
||||
import { useGameLogStore } from './gameLog';
|
||||
import { useGameStore } from './game';
|
||||
import { useGeneralSettingsStore } from './settings/general';
|
||||
import { useGlobalSearchStore } from './globalSearch';
|
||||
import { useGroupStore } from './group';
|
||||
import { useInstanceStore } from './instance';
|
||||
import { useInviteStore } from './invite';
|
||||
@@ -163,7 +164,8 @@ export function createGlobalStores() {
|
||||
auth: useAuthStore(),
|
||||
vrcStatus: useVrcStatusStore(),
|
||||
charts: useChartsStore(),
|
||||
modal: useModalStore()
|
||||
modal: useModalStore(),
|
||||
globalSearch: useGlobalSearchStore()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -202,5 +204,6 @@ export {
|
||||
useSharedFeedStore,
|
||||
useUpdateLoopStore,
|
||||
useVrcStatusStore,
|
||||
useModalStore
|
||||
useModalStore,
|
||||
useGlobalSearchStore
|
||||
};
|
||||
|
||||
+38
-6
@@ -14,9 +14,8 @@ import {
|
||||
} from '../shared/utils';
|
||||
import { instanceRequest, miscRequest, worldRequest } from '../api';
|
||||
import { database } from '../service/database';
|
||||
import { useAvatarStore } from './avatar';
|
||||
import { processBulk } from '../service/request';
|
||||
import { useFavoriteStore } from './favorite';
|
||||
import { useGroupStore } from './group';
|
||||
import { useInstanceStore } from './instance';
|
||||
import { useLocationStore } from './location';
|
||||
import { useUiStore } from './ui';
|
||||
@@ -28,8 +27,6 @@ export const useWorldStore = defineStore('World', () => {
|
||||
const favoriteStore = useFavoriteStore();
|
||||
const instanceStore = useInstanceStore();
|
||||
const userStore = useUserStore();
|
||||
const avatarStore = useAvatarStore();
|
||||
const groupStore = useGroupStore();
|
||||
const uiStore = useUiStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -64,9 +61,12 @@ export const useWorldStore = defineStore('World', () => {
|
||||
|
||||
watch(
|
||||
() => watchState.isLoggedIn,
|
||||
() => {
|
||||
(isLoggedIn) => {
|
||||
worldDialog.visible = false;
|
||||
cachedWorlds.clear();
|
||||
if (isLoggedIn) {
|
||||
preloadOwnWorlds();
|
||||
}
|
||||
},
|
||||
{ flush: 'sync' }
|
||||
);
|
||||
@@ -210,6 +210,9 @@ export const useWorldStore = defineStore('World', () => {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function updateVRChatWorldCache() {
|
||||
const D = worldDialog;
|
||||
if (D.visible) {
|
||||
@@ -228,6 +231,10 @@ export const useWorldStore = defineStore('World', () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param WorldCache
|
||||
*/
|
||||
function cleanupWorldCache(WorldCache) {
|
||||
const maxCacheSize = 10000;
|
||||
|
||||
@@ -339,11 +346,36 @@ export const useWorldStore = defineStore('World', () => {
|
||||
return ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload all own worlds into cache at startup for global search.
|
||||
*/
|
||||
async function preloadOwnWorlds() {
|
||||
const params = {
|
||||
n: 50,
|
||||
offset: 0,
|
||||
sort: 'updated',
|
||||
order: 'descending',
|
||||
releaseStatus: 'all',
|
||||
user: 'me'
|
||||
};
|
||||
await processBulk({
|
||||
fn: (p) => worldRequest.getWorlds(p),
|
||||
N: -1,
|
||||
params,
|
||||
handle: (args) => {
|
||||
for (const json of args.json) {
|
||||
applyWorld(json);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
worldDialog,
|
||||
cachedWorlds,
|
||||
showWorldDialog,
|
||||
updateVRChatWorldCache,
|
||||
applyWorld
|
||||
applyWorld,
|
||||
preloadOwnWorlds
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user