refactor store

This commit is contained in:
pa
2026-03-10 15:25:23 +09:00
parent d7220baaf6
commit 95c4a1d3e6
82 changed files with 3243 additions and 3066 deletions

View File

@@ -1,5 +1,5 @@
import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import { i18n } from '../plugin/i18n';
import { AppDebug } from '../service/appConfig';
import { useAdvancedSettingsStore } from '../stores/settings/advanced';
@@ -17,7 +17,7 @@ export async function runHandleAutoLoginFlow({
} = {}) {
const authStore = useAuthStore();
const advancedSettingsStore = useAdvancedSettingsStore();
const { t } = useI18n();
const t = i18n.global.t;
if (authStore.attemptingAutoLogin) {
return;

View File

@@ -1,4 +1,4 @@
import { useI18n } from 'vue-i18n';
import { i18n } from '../plugin/i18n';
import Noty from 'noty';
@@ -9,6 +9,7 @@ import { useAuthStore } from '../stores/auth';
import { useNotificationStore } from '../stores/notification';
import { useUpdateLoopStore } from '../stores/updateLoop';
import { useUserStore } from '../stores/user';
import { applyCurrentUser } from './userCoordinator';
import { watchState } from '../service/watchState';
import configRepository from '../service/config';
@@ -21,7 +22,7 @@ export async function runLogoutFlow() {
const authStore = useAuthStore();
const userStore = useUserStore();
const notificationStore = useNotificationStore();
const { t } = useI18n();
const t = i18n.global.t;
if (watchState.isLoggedIn) {
new Noty({
@@ -56,6 +57,6 @@ export function runLoginSuccessFlow(json) {
const userStore = useUserStore();
updateLoopStore.setNextCurrentUserRefresh(420); // 7mins
userStore.applyCurrentUser(json);
applyCurrentUser(json);
initWebsocket();
}

View File

@@ -0,0 +1,649 @@
import { nextTick } from 'vue';
import { toast } from 'vue-sonner';
import { i18n } from '../plugin/i18n';
import {
createDefaultAvatarRef,
extractFileId,
getAvailablePlatforms,
getBundleDateSize,
getPlatformInfo,
replaceBioSymbols,
sanitizeEntityJson,
storeAvatarImage
} from '../shared/utils';
import { avatarRequest, miscRequest, queryRequest } from '../api';
import { AppDebug } from '../service/appConfig';
import { database } from '../service/database';
import { patchAvatarFromEvent } from '../queries';
import { processBulk } from '../service/request';
import { applyFavorite } from './favoriteCoordinator';
import { refreshUserDialogAvatars, showUserDialog } from './userCoordinator';
import { useAdvancedSettingsStore } from '../stores/settings/advanced';
import { useAvatarProviderStore } from '../stores/avatarProvider';
import { useAvatarStore } from '../stores/avatar';
import { useFavoriteStore } from '../stores/favorite';
import { useModalStore } from '../stores/modal';
import { useUiStore } from '../stores/ui';
import { useUserStore } from '../stores/user';
import { useVRCXUpdaterStore } from '../stores/vrcxUpdater';
import webApiService from '../service/webapi';
/**
* @param {object} json
* @returns {object} ref
*/
export function applyAvatar(json) {
const avatarStore = useAvatarStore();
const favoriteStore = useFavoriteStore();
sanitizeEntityJson(json, ['name', 'description']);
let ref = avatarStore.cachedAvatars.get(json.id);
if (typeof ref === 'undefined') {
ref = createDefaultAvatarRef(json);
avatarStore.cachedAvatars.set(ref.id, ref);
} else {
const { unityPackages } = ref;
Object.assign(ref, json);
if (
json.unityPackages?.length > 0 &&
unityPackages.length > 0 &&
!json.unityPackages[0].assetUrl
) {
ref.unityPackages = unityPackages;
}
}
for (const listing of ref.publishedListings) {
listing.displayName = replaceBioSymbols(listing.displayName);
listing.description = replaceBioSymbols(listing.description);
}
applyFavorite('avatar', ref.id);
if (favoriteStore.localAvatarFavoritesList.includes(ref.id)) {
const avatarRef = ref;
favoriteStore.syncLocalAvatarFavoriteRef(avatarRef);
// update db cache
database.addAvatarToCache(avatarRef);
}
patchAvatarFromEvent(ref);
return ref;
}
/**
*
* @param {string} avatarId
* @param options
* @returns
*/
export function showAvatarDialog(avatarId, options = {}) {
const avatarStore = useAvatarStore();
const uiStore = useUiStore();
const favoriteStore = useFavoriteStore();
const userStore = useUserStore();
const t = i18n.global.t;
const D = avatarStore.avatarDialog;
const forceRefresh = Boolean(options?.forceRefresh);
const isMainDialogOpen = uiStore.openDialog({
type: 'avatar',
id: avatarId
});
D.visible = true;
if (isMainDialogOpen && D.id === avatarId && !forceRefresh) {
uiStore.setDialogCrumbLabel('avatar', D.id, D.ref?.name || D.id);
nextTick(() => (D.loading = false));
return;
}
D.loading = true;
D.id = avatarId;
D.inCache = false;
D.cacheSize = '';
D.cacheLocked = false;
D.cachePath = '';
D.fileAnalysis = {};
D.isQuestFallback = false;
D.isPC = false;
D.isQuest = false;
D.isIos = false;
D.hasImposter = false;
D.imposterVersion = '';
D.platformInfo = {};
D.galleryImages = [];
D.galleryLoading = true;
D.isFavorite =
favoriteStore.getCachedFavoritesByObjectId(avatarId) ||
(userStore.isLocalUserVrcPlusSupporter &&
favoriteStore.localAvatarFavoritesList.includes(avatarId));
D.isBlocked = avatarStore.cachedAvatarModerations.has(avatarId);
const ref2 = avatarStore.cachedAvatars.get(avatarId);
if (typeof ref2 !== 'undefined') {
D.ref = ref2;
uiStore.setDialogCrumbLabel('avatar', D.id, D.ref?.name || D.id);
nextTick(() => (D.loading = false));
}
const loadAvatarRequest = forceRefresh
? avatarRequest.getAvatar({ avatarId })
: queryRequest.fetch('avatar', { avatarId });
loadAvatarRequest
.then((args) => {
const ref = applyAvatar(args.json);
D.ref = ref;
uiStore.setDialogCrumbLabel(
'avatar',
D.id,
D.ref?.name || D.id
);
avatarStore.getAvatarGallery(avatarId);
avatarStore.updateVRChatAvatarCache();
if (/quest/.test(ref.tags)) {
D.isQuestFallback = true;
}
const { isPC, isQuest, isIos } = getAvailablePlatforms(
ref.unityPackages
);
D.isPC = isPC;
D.isQuest = isQuest;
D.isIos = isIos;
D.platformInfo = getPlatformInfo(ref.unityPackages);
for (let i = ref.unityPackages.length - 1; i > -1; i--) {
const unityPackage = ref.unityPackages[i];
if (unityPackage.variant === 'impostor') {
D.hasImposter = true;
D.imposterVersion = unityPackage.impostorizerVersion;
break;
}
}
if (Object.keys(D.fileAnalysis).length === 0) {
getBundleDateSize(ref);
}
})
.catch((err) => {
D.loading = false;
D.id = null;
D.visible = false;
uiStore.jumpBackDialogCrumb();
toast.error(t('message.api_handler.avatar_private_or_deleted'));
throw err;
})
.finally(() => {
nextTick(() => (D.loading = false));
});
}
/**
*
* @returns {Promise<void>}
*/
export async function getAvatarHistory() {
const avatarStore = useAvatarStore();
const userStore = useUserStore();
const historyArray = await database.getAvatarHistory(
userStore.currentUser.id
);
for (let i = 0; i < historyArray.length; i++) {
const avatar = historyArray[i];
if (avatar.authorId === userStore.currentUser.id) {
continue;
}
applyAvatar(avatar);
}
avatarStore.avatarHistory = historyArray;
}
/**
* @param {string} avatarId
*/
export function addAvatarToHistory(avatarId) {
const avatarStore = useAvatarStore();
const userStore = useUserStore();
avatarRequest
.getAvatar({ avatarId })
.then((args) => {
const ref = applyAvatar(args.json);
database.addAvatarToCache(ref);
database.addAvatarToHistory(ref.id);
if (ref.authorId === userStore.currentUser.id) {
return;
}
const historyArray = avatarStore.avatarHistory;
for (let i = 0; i < historyArray.length; ++i) {
if (historyArray[i].id === ref.id) {
historyArray.splice(i, 1);
}
}
avatarStore.avatarHistory.unshift(ref);
})
.catch((err) => {
console.error('Failed to add avatar to history:', err);
});
}
/**
*
*/
export function promptClearAvatarHistory() {
const avatarStore = useAvatarStore();
const modalStore = useModalStore();
const t = i18n.global.t;
modalStore
.confirm({
description: t('confirm.clear_avatar_history'),
title: 'Confirm'
})
.then(({ ok }) => {
if (!ok) return;
avatarStore.clearAvatarHistory();
})
.catch(() => {});
}
/**
*
* @param {string} imageUrl
* @returns {Promise<object>}
*/
export async function getAvatarName(imageUrl) {
const avatarStore = useAvatarStore();
const fileId = extractFileId(imageUrl);
if (!fileId) {
return {
ownerId: '',
avatarName: '-'
};
}
if (avatarStore.cachedAvatarNames.has(fileId)) {
return avatarStore.cachedAvatarNames.get(fileId);
}
try {
const args = await miscRequest.getFile({ fileId });
return storeAvatarImage(args, avatarStore.cachedAvatarNames);
} catch (error) {
console.error('Failed to get avatar images:', error);
return {
ownerId: '',
avatarName: '-'
};
}
}
/**
*
* @param type
* @param search
*/
export async function lookupAvatars(type, search) {
const avatarProviderStore = useAvatarProviderStore();
const vrcxUpdaterStore = useVRCXUpdaterStore();
const avatars = new Map();
if (type === 'search') {
try {
const url = `${
avatarProviderStore.avatarRemoteDatabaseProvider
}?${type}=${encodeURIComponent(search)}&n=5000`;
const response = await webApiService.execute({
url,
method: 'GET',
headers: {
Referer: 'https://vrcx.app',
'VRCX-ID': vrcxUpdaterStore.vrcxId
}
});
const json = JSON.parse(response.data);
if (AppDebug.debugWebRequests) {
console.log(url, json, response);
}
if (response.status === 200 && typeof json === 'object') {
json.forEach((avatar) => {
if (!avatars.has(avatar.Id)) {
const ref = {
authorId: '',
authorName: '',
name: '',
description: '',
id: '',
imageUrl: '',
thumbnailImageUrl: '',
created_at: '0001-01-01T00:00:00.0000000Z',
updated_at: '0001-01-01T00:00:00.0000000Z',
releaseStatus: 'public',
...avatar
};
avatars.set(ref.id, ref);
}
});
} else {
throw new Error(`Error: ${response.data}`);
}
} catch (err) {
const msg = `Avatar search failed for ${search} with ${avatarProviderStore.avatarRemoteDatabaseProvider}\n${err}`;
console.error(msg);
toast.error(msg);
}
} else if (type === 'authorId') {
const length =
avatarProviderStore.avatarRemoteDatabaseProviderList.length;
for (let i = 0; i < length; ++i) {
const url =
avatarProviderStore.avatarRemoteDatabaseProviderList[i];
const avatarArray = await lookupAvatarsByAuthor(url, search);
avatarArray.forEach((avatar) => {
if (!avatars.has(avatar.id)) {
avatars.set(avatar.id, avatar);
}
});
}
}
return avatars;
}
/**
*
* @param authorId
* @param fileId
*/
export async function lookupAvatarByImageFileId(authorId, fileId) {
const avatarProviderStore = useAvatarProviderStore();
for (const providerUrl of avatarProviderStore.avatarRemoteDatabaseProviderList) {
const avatar = await lookupAvatarByFileId(providerUrl, fileId);
if (avatar?.id) {
return avatar.id;
}
}
for (const providerUrl of avatarProviderStore.avatarRemoteDatabaseProviderList) {
const avatarArray = await lookupAvatarsByAuthor(
providerUrl,
authorId
);
for (const avatar of avatarArray) {
if (extractFileId(avatar.imageUrl) === fileId) {
return avatar.id;
}
}
}
return null;
}
/**
*
* @param providerUrl
* @param fileId
*/
async function lookupAvatarByFileId(providerUrl, fileId) {
const vrcxUpdaterStore = useVRCXUpdaterStore();
try {
const url = `${providerUrl}?fileId=${encodeURIComponent(fileId)}`;
const response = await webApiService.execute({
url,
method: 'GET',
headers: {
Referer: 'https://vrcx.app',
'VRCX-ID': vrcxUpdaterStore.vrcxId
}
});
const json = JSON.parse(response.data);
if (AppDebug.debugWebRequests) {
console.log(url, json, response);
}
if (response.status === 200 && typeof json === 'object') {
const ref = {
authorId: '',
authorName: '',
name: '',
description: '',
id: '',
imageUrl: '',
thumbnailImageUrl: '',
created_at: '0001-01-01T00:00:00.0000000Z',
updated_at: '0001-01-01T00:00:00.0000000Z',
releaseStatus: 'public',
...json
};
return ref;
} else {
return null;
}
} catch (err) {
// ignore errors for now, not all providers support this lookup type
return null;
}
}
/**
*
* @param providerUrl
* @param authorId
*/
async function lookupAvatarsByAuthor(providerUrl, authorId) {
const vrcxUpdaterStore = useVRCXUpdaterStore();
const avatars = [];
if (!providerUrl || !authorId) {
return avatars;
}
const url = `${providerUrl}?authorId=${encodeURIComponent(authorId)}`;
try {
const response = await webApiService.execute({
url,
method: 'GET',
headers: {
Referer: 'https://vrcx.app',
'VRCX-ID': vrcxUpdaterStore.vrcxId
}
});
const json = JSON.parse(response.data);
if (AppDebug.debugWebRequests) {
console.log(url, json, response);
}
if (response.status === 200 && typeof json === 'object') {
json.forEach((avatar) => {
const ref = {
authorId: '',
authorName: '',
name: '',
description: '',
id: '',
imageUrl: '',
thumbnailImageUrl: '',
created_at: '0001-01-01T00:00:00.0000000Z',
updated_at: '0001-01-01T00:00:00.0000000Z',
releaseStatus: 'public',
...avatar
};
avatars.push(ref);
});
} else {
throw new Error(`Error: ${response.data}`);
}
} catch (err) {
const msg = `Avatar lookup failed for ${authorId} with ${url}\n${err}`;
console.error(msg);
toast.error(msg);
}
return avatars;
}
/**
*
* @param id
*/
export function selectAvatarWithConfirmation(id) {
const modalStore = useModalStore();
const t = i18n.global.t;
modalStore
.confirm({
description: t('confirm.select_avatar'),
title: 'Confirm'
})
.then(({ ok }) => {
if (!ok) return;
selectAvatarWithoutConfirmation(id);
})
.catch(() => {});
}
/**
*
* @param id
*/
export async function selectAvatarWithoutConfirmation(id) {
const userStore = useUserStore();
if (userStore.currentUser.currentAvatar === id) {
toast.info('Avatar already selected');
return;
}
return avatarRequest
.selectAvatar({
avatarId: id
})
.then(() => {
toast.success('Avatar changed');
});
}
/**
*
* @param fileId
*/
export function checkAvatarCache(fileId) {
const avatarStore = useAvatarStore();
let avatarId = '';
for (let ref of avatarStore.cachedAvatars.values()) {
if (extractFileId(ref.imageUrl) === fileId) {
avatarId = ref.id;
}
}
return avatarId;
}
/**
*
* @param fileId
* @param ownerUserId
*/
export async function checkAvatarCacheRemote(fileId, ownerUserId) {
const advancedSettingsStore = useAdvancedSettingsStore();
const avatarStore = useAvatarStore();
const t = i18n.global.t;
if (advancedSettingsStore.avatarRemoteDatabase) {
try {
toast.dismiss(avatarStore.loadingToastId);
avatarStore.loadingToastId = toast.loading(
t('message.avatar_lookup.loading')
);
const avatarId = await lookupAvatarByImageFileId(
ownerUserId,
fileId
);
return avatarId;
} catch (err) {
console.error('Failed to lookup avatar by image file id:', err);
} finally {
toast.dismiss(avatarStore.loadingToastId);
}
}
return null;
}
/**
*
* @param refUserId
* @param ownerUserId
* @param currentAvatarImageUrl
*/
export async function showAvatarAuthorDialog(
refUserId,
ownerUserId,
currentAvatarImageUrl
) {
const userStore = useUserStore();
const t = i18n.global.t;
const fileId = extractFileId(currentAvatarImageUrl);
if (!fileId) {
toast.error(t('message.avatar_lookup.failed'));
} else if (refUserId === userStore.currentUser.id) {
showAvatarDialog(userStore.currentUser.currentAvatar);
} else {
let avatarId = checkAvatarCache(fileId);
let avatarInfo;
if (!avatarId) {
avatarInfo = await getAvatarName(currentAvatarImageUrl);
if (avatarInfo.ownerId === userStore.currentUser.id) {
await refreshUserDialogAvatars(fileId);
return;
}
}
if (!avatarId) {
avatarId = await checkAvatarCacheRemote(fileId, ownerUserId);
}
if (!avatarId) {
if (ownerUserId === refUserId) {
toast.warning(
t('message.avatar_lookup.private_or_not_found')
);
} else {
toast.warning(t('message.avatar_lookup.not_found'));
showUserDialog(avatarInfo.ownerId);
}
}
if (avatarId) {
showAvatarDialog(avatarId);
}
}
}
/**
*
* @param avatarId
*/
export function addAvatarWearTime(avatarId) {
const userStore = useUserStore();
if (!userStore.currentUser.$previousAvatarSwapTime || !avatarId) {
return;
}
const timeSpent =
Date.now() - userStore.currentUser.$previousAvatarSwapTime;
database.addAvatarTimeSpent(avatarId, timeSpent);
}
/**
* Preload all own avatars into cache at startup for global search.
*/
export 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);
}
}
});
}

View File

@@ -2,10 +2,12 @@ import { toast } from 'vue-sonner';
import { useFavoriteStore } from '../stores/favorite';
import { useAppearanceSettingsStore } from '../stores/settings/appearance';
import { useAvatarStore } from '../stores/avatar';
import { applyAvatar } from './avatarCoordinator';
import { useFriendStore } from '../stores/friend';
import { useGeneralSettingsStore } from '../stores/settings/general';
import { useUserStore } from '../stores/user';
import { useWorldStore } from '../stores/world';
import { applyWorld } from './worldCoordinator';
import { runUpdateFriendFlow } from './friendPresenceCoordinator';
import { avatarRequest, favoriteRequest, queryRequest } from '../api';
import { database } from '../service/database';
@@ -177,7 +179,7 @@ export function handleFavoriteWorldList(args) {
if (json.id === '???') {
continue;
}
worldStore.applyWorld(json);
applyWorld(json);
}
}
@@ -191,7 +193,7 @@ export function handleFavoriteAvatarList(args) {
if (json.releaseStatus === 'hidden') {
continue;
}
avatarStore.applyAvatar(json);
applyAvatar(json);
}
}
@@ -652,7 +654,7 @@ export async function getLocalWorldFavorites() {
for (let i = 0; i < worldCache.length; ++i) {
const ref = worldCache[i];
if (!worldStore.cachedWorlds.has(ref.id)) {
worldStore.applyWorld(ref);
applyWorld(ref);
}
}
@@ -909,7 +911,7 @@ export async function getLocalAvatarFavorites() {
for (let i = 0; i < avatarCache.length; ++i) {
const ref = avatarCache[i];
if (!avatarStore.cachedAvatars.has(ref.id)) {
avatarStore.applyAvatar(ref);
applyAvatar(ref);
}
}

View File

@@ -7,6 +7,7 @@ import { useAuthStore } from '../stores/auth';
import { useFriendStore } from '../stores/friend';
import { useUpdateLoopStore } from '../stores/updateLoop';
import { useUserStore } from '../stores/user';
import { getCurrentUser } from './userCoordinator';
import { watchState } from '../service/watchState';
import configRepository from '../service/config';
@@ -21,7 +22,7 @@ export async function runRefreshFriendsListFlow() {
// If we just got user less then 2 min before code call, don't call it again
if (updateLoopStore.nextCurrentUserRefresh < 300) {
await userStore.getCurrentUser();
await getCurrentUser();
}
await friendStore.refreshFriends();
reconnectWebSocket();

View File

@@ -7,6 +7,7 @@ import {
import { database } from '../service/database';
import { useAdvancedSettingsStore } from '../stores/settings/advanced';
import { useAvatarStore } from '../stores/avatar';
import { addAvatarWearTime } from './avatarCoordinator';
import { useGameLogStore } from '../stores/gameLog';
import { useGameStore } from '../stores/game';
import { useInstanceStore } from '../stores/instance';
@@ -47,7 +48,7 @@ export async function runGameRunningChangedFlow(isGameRunning) {
runAutoVRChatCacheManagementFlow();
runCheckIfGameCrashedFlow();
updateLoopStore.setIpcTimeout(0);
avatarStore.addAvatarWearTime(userStore.currentUser.currentAvatar);
addAvatarWearTime(userStore.currentUser.currentAvatar);
}
runLastLocationResetFlow();

View File

@@ -0,0 +1,957 @@
import { nextTick } from 'vue';
import { toast } from 'vue-sonner';
import { i18n } from '../plugin/i18n';
import {
convertFileUrlToImageUrl,
createDefaultGroupRef,
sanitizeEntityJson,
replaceBioSymbols
} from '../shared/utils';
import { groupRequest, instanceRequest, queryRequest } from '../api';
import { database } from '../service/database';
import { groupDialogFilterOptions } from '../shared/constants/';
import { patchGroupFromEvent } from '../queries';
import { useGameStore } from '../stores/game';
import { useInstanceStore } from '../stores/instance';
import { useModalStore } from '../stores/modal';
import { useNotificationStore } from '../stores/notification';
import { useUiStore } from '../stores/ui';
import { useUserStore } from '../stores/user';
import { useGroupStore } from '../stores/group';
import { watchState } from '../service/watchState';
import configRepository from '../service/config';
import * as workerTimers from 'worker-timers';
// ─── Internal helpers (not exported) ─────────────────────────────────────────
/**
* @param ref
*/
function applyGroupLanguage(ref) {
const userStore = useUserStore();
ref.$languages = [];
const { languages } = ref;
if (!languages) {
return;
}
for (const language of languages) {
const value = userStore.subsetOfLanguages[language];
if (typeof value === 'undefined') {
continue;
}
ref.$languages.push({
key: language,
value
});
}
}
// ─── Core entity application ─────────────────────────────────────────────────
/**
*
* @param {object} json
* @returns {object} ref
*/
export function applyGroup(json) {
const groupStore = useGroupStore();
const userStore = useUserStore();
let ref = groupStore.cachedGroups.get(json.id);
sanitizeEntityJson(json, ['rules', 'name', 'description']);
if (typeof ref === 'undefined') {
ref = createDefaultGroupRef(json);
groupStore.cachedGroups.set(ref.id, ref);
} else {
if (groupStore.currentUserGroups.has(ref.id)) {
// compare group props
if (
ref.ownerId &&
json.ownerId &&
ref.ownerId !== json.ownerId
) {
// owner changed
groupOwnerChange(json, ref.ownerId, json.ownerId);
}
if (ref.name && json.name && ref.name !== json.name) {
// name changed
groupChange(
json,
`Name changed from ${ref.name} to ${json.name}`
);
}
if (ref.myMember?.roleIds && json.myMember?.roleIds) {
const oldRoleIds = ref.myMember.roleIds;
const newRoleIds = json.myMember.roleIds;
if (
oldRoleIds.length !== newRoleIds.length ||
!oldRoleIds.every(
(value, index) => value === newRoleIds[index]
)
) {
// roleIds changed
groupRoleChange(
json,
ref.roles,
json.roles,
oldRoleIds,
newRoleIds
);
}
}
}
if (json.myMember) {
if (typeof json.myMember.roleIds === 'undefined') {
// keep roleIds
json.myMember.roleIds = ref.myMember.roleIds;
}
Object.assign(ref.myMember, json.myMember);
}
Object.assign(ref, json);
}
// update myMember without fetching member
if (typeof json.memberVisibility !== 'undefined') {
ref.myMember.visibility = json.memberVisibility;
}
if (typeof json.isRepresenting !== 'undefined') {
ref.myMember.isRepresenting = json.isRepresenting;
}
if (typeof json.membershipStatus !== 'undefined') {
ref.myMember.membershipStatus = json.membershipStatus;
}
if (typeof json.roleIds !== 'undefined') {
ref.myMember.roleIds = json.roleIds;
}
ref.$url = `https://vrc.group/${ref.shortCode}.${ref.discriminator}`;
applyGroupLanguage(ref);
const currentUserGroupRef = groupStore.currentUserGroups.get(ref.id);
if (currentUserGroupRef) {
groupStore.currentUserGroups.set(ref.id, ref);
}
const D = groupStore.groupDialog;
if (D.visible && D.id === ref.id) {
D.inGroup = ref.membershipStatus === 'member';
D.ref = ref;
}
patchGroupFromEvent(ref);
return ref;
}
/**
*
* @param {object} json
* @returns {*}
*/
export function applyGroupMember(json) {
const userStore = useUserStore();
const groupStore = useGroupStore();
let ref;
if (typeof json?.user !== 'undefined') {
if (json.userId === userStore.currentUser.id) {
json.user = userStore.currentUser;
json.$displayName = userStore.currentUser.displayName;
} else {
ref = userStore.cachedUsers.get(json.user.id);
if (typeof ref !== 'undefined') {
json.user = ref;
json.$displayName = ref.displayName;
} else {
json.$displayName = json.user?.displayName;
}
}
}
// update myMember without fetching member
if (json?.userId === userStore.currentUser.id) {
ref = groupStore.cachedGroups.get(json.groupId);
if (typeof ref !== 'undefined') {
const newJson = {
id: json.groupId,
memberVisibility: json.visibility,
isRepresenting: json.isRepresenting,
isSubscribedToAnnouncements:
json.isSubscribedToAnnouncements,
joinedAt: json.joinedAt,
roleIds: json.roleIds,
membershipStatus: json.membershipStatus
};
applyGroup(newJson);
}
}
return json;
}
// ─── Group change notifications ──────────────────────────────────────────────
/**
*
* @param ref
* @param message
*/
function groupChange(ref, message) {
const groupStore = useGroupStore();
const notificationStore = useNotificationStore();
if (!groupStore.currentUserGroupsInit) {
return;
}
// oh the level of cursed for compibility
const json = {
id: Math.random().toString(36),
type: 'groupChange',
senderUserId: ref.id,
senderUsername: ref.name,
imageUrl: ref.iconUrl,
details: {
imageUrl: ref.iconUrl
},
message,
created_at: new Date().toJSON()
};
notificationStore.handleNotification({
json,
params: {
notificationId: json.id
}
});
// delay to wait for json to be assigned to ref
workerTimers.setTimeout(() => saveCurrentUserGroups(), 100);
}
/**
*
* @param {object }ref
* @param {string} oldUserId
* @param {string} newUserId
* @returns {Promise<void>}
*/
async function groupOwnerChange(ref, oldUserId, newUserId) {
const oldUser = await queryRequest.fetch('user', {
userId: oldUserId
});
const newUser = await queryRequest.fetch('user', {
userId: newUserId
});
const oldDisplayName = oldUser?.ref?.displayName;
const newDisplayName = newUser?.ref?.displayName;
groupChange(
ref,
`Owner changed from ${oldDisplayName} to ${newDisplayName}`
);
}
/**
*
* @param {object} ref
* @param {Array} oldRoles
* @param {Array} newRoles
* @param {Array} oldRoleIds
* @param {Array} newRoleIds
*/
function groupRoleChange(ref, oldRoles, newRoles, oldRoleIds, newRoleIds) {
// check for removed/added roleIds
for (const roleId of oldRoleIds) {
if (!newRoleIds.includes(roleId)) {
let roleName = '';
const role = oldRoles.find(
(fineRole) => fineRole.id === roleId
);
if (role) {
roleName = role.name;
}
groupChange(ref, `Role ${roleName} removed`);
}
}
if (typeof newRoles !== 'undefined') {
for (const roleId of newRoleIds) {
if (!oldRoleIds.includes(roleId)) {
let roleName = '';
const role = newRoles.find(
(fineRole) => fineRole.id === roleId
);
if (role) {
roleName = role.name;
}
groupChange(ref, `Role ${roleName} added`);
}
}
}
}
// ─── Dialog flows ────────────────────────────────────────────────────────────
/**
*
* @param groupId
* @param options
*/
export function showGroupDialog(groupId, options = {}) {
const t = i18n.global.t;
const groupStore = useGroupStore();
const uiStore = useUiStore();
const instanceStore = useInstanceStore();
if (!groupId) {
return;
}
const forceRefresh = Boolean(options?.forceRefresh);
const isMainDialogOpen = uiStore.openDialog({
type: 'group',
id: groupId
});
const D = groupStore.groupDialog;
D.visible = true;
if (isMainDialogOpen && D.id === groupId && !forceRefresh) {
uiStore.setDialogCrumbLabel('group', D.id, D.ref?.name || D.id);
instanceStore.applyGroupDialogInstances();
D.loading = false;
return;
}
D.loading = true;
D.id = groupId;
D.inGroup = false;
D.ownerDisplayName = '';
D.announcement = {};
D.posts = [];
D.postsFiltered = [];
D.instances = [];
D.memberRoles = [];
D.lastVisit = '';
D.memberSearch = '';
D.memberSearchResults = [];
D.galleries = {};
D.members = [];
D.memberFilter = groupDialogFilterOptions.everyone;
D.calendar = [];
const loadGroupRequest = groupRequest.getGroup({
groupId,
includeRoles: true
});
loadGroupRequest
.catch((err) => {
D.loading = false;
D.id = null;
D.visible = false;
uiStore.jumpBackDialogCrumb();
toast.error(t('message.group.load_failed'));
throw err;
})
.then((args) => {
const ref = args.ref || applyGroup(args.json);
if (groupId === ref.id) {
D.ref = ref;
uiStore.setDialogCrumbLabel(
'group',
D.id,
D.ref?.name || D.id
);
D.inGroup = ref.membershipStatus === 'member';
D.ownerDisplayName = ref.ownerId;
D.visible = true;
D.loading = false;
queryRequest
.fetch('user', {
userId: ref.ownerId
})
.then((args1) => {
D.ownerDisplayName = args1.ref.displayName;
});
database.getLastGroupVisit(D.ref.name).then((r) => {
if (D.id === ref.id) {
D.lastVisit = r.created_at;
}
});
instanceStore.applyGroupDialogInstances();
getGroupDialogGroup(groupId, ref);
}
});
}
/**
*
* @param groupId
* @param {object} [existingRef]
* @returns { Promise<object> }
*/
export function getGroupDialogGroup(groupId, existingRef) {
const groupStore = useGroupStore();
const instanceStore = useInstanceStore();
const D = groupStore.groupDialog;
D.isGetGroupDialogGroupLoading = false;
const refPromise = existingRef
? Promise.resolve({ ref: existingRef })
: queryRequest
.fetch('group', { groupId, includeRoles: true })
.then((args) => ({ ref: applyGroup(args.json), args }));
return refPromise
.catch((err) => {
throw err;
})
.then((result) => {
const ref = result.ref;
if (D.id === ref.id) {
D.loading = false;
D.ref = ref;
D.inGroup = ref.membershipStatus === 'member';
D.memberRoles = [];
for (const role of ref.roles) {
if (
D.ref &&
D.ref.myMember &&
Array.isArray(D.ref.myMember.roleIds) &&
D.ref.myMember.roleIds.includes(role.id)
) {
D.memberRoles.push(role);
}
}
groupStore.getAllGroupPosts({
groupId
});
D.isGetGroupDialogGroupLoading = true;
groupRequest
.getGroupInstances({
groupId
})
.then((args) => {
if (groupStore.groupDialog.id === args.params.groupId) {
instanceStore.applyGroupDialogInstances(
args.json.instances
);
}
for (const json of args.json.instances) {
instanceStore.applyInstance(json);
queryRequest
.fetch('world', {
worldId: json.world.id
})
.then((args1) => {
json.world = args1.ref;
});
// get queue size etc
instanceRequest.getInstance({
worldId: json.worldId,
instanceId: json.instanceId
});
}
});
queryRequest
.fetch('groupCalendar', { groupId })
.then((args) => {
if (groupStore.groupDialog.id === args.params.groupId) {
D.calendar = args.json.results;
for (const event of D.calendar) {
Object.assign(event, groupStore.applyGroupEvent(event));
// fetch again for isFollowing
queryRequest
.fetch('groupCalendarEvent', {
groupId,
eventId: event.id
})
.then((args) => {
Object.assign(
event,
groupStore.applyGroupEvent(args.json)
);
});
}
}
});
}
nextTick(() => (D.isGetGroupDialogGroupLoading = false));
return result.args || result;
});
}
// ─── Group lifecycle flows ───────────────────────────────────────────────────
/**
*
* @param {object} ref
*/
export function applyPresenceGroups(ref) {
const groupStore = useGroupStore();
if (!groupStore.currentUserGroupsInit) {
// wait for init before diffing
return;
}
const groups = ref.presence?.groups;
if (!groups) {
console.error('applyPresenceGroups: invalid groups', ref);
return;
}
if (groups.length === 0) {
// as it turns out, this is not the most trust worthly source of info
return;
}
// update group list
for (const groupId of groups) {
if (!groupStore.currentUserGroups.has(groupId)) {
onGroupJoined(groupId);
}
}
for (const groupId of groupStore.currentUserGroups.keys()) {
if (!groups.includes(groupId)) {
onGroupLeft(groupId);
}
}
}
/**
*
* @param {string} groupId
*/
export function onGroupJoined(groupId) {
const groupStore = useGroupStore();
if (!groupStore.currentUserGroups.has(groupId)) {
groupStore.currentUserGroups.set(groupId, {
id: groupId,
name: '',
iconUrl: ''
});
groupRequest
.getGroup({ groupId, includeRoles: true })
.then((args) => {
applyGroup(args.json);
saveCurrentUserGroups();
return args;
});
}
}
/**
*
* @param {string} groupId
*/
export async function onGroupLeft(groupId) {
const groupStore = useGroupStore();
const args = await groupRequest.getGroup({ groupId });
const ref = applyGroup(args.json);
if (ref.membershipStatus === 'member') {
// wtf, not trusting presence
console.error(
`onGroupLeft: presence lied, still a member of ${groupId}`
);
return;
}
if (groupStore.groupDialog.visible && groupStore.groupDialog.id === groupId) {
showGroupDialog(groupId);
}
if (groupStore.currentUserGroups.has(groupId)) {
groupStore.currentUserGroups.delete(groupId);
groupChange(ref, 'Left group');
// delay to wait for json to be assigned to ref
workerTimers.setTimeout(() => saveCurrentUserGroups(), 100);
}
}
// ─── User group management ───────────────────────────────────────────────────
/**
*
*/
export function saveCurrentUserGroups() {
const groupStore = useGroupStore();
const userStore = useUserStore();
if (!groupStore.currentUserGroupsInit) {
return;
}
const groups = [];
for (const ref of groupStore.currentUserGroups.values()) {
groups.push({
id: ref.id,
name: ref.name,
ownerId: ref.ownerId,
iconUrl: ref.iconUrl,
roles: ref.roles,
roleIds: ref.myMember?.roleIds
});
}
configRepository.setString(
`VRCX_currentUserGroups_${userStore.currentUser.id}`,
JSON.stringify(groups)
);
}
/**
*
* @param userId
* @param groups
*/
export async function loadCurrentUserGroups(userId, groups) {
const groupStore = useGroupStore();
const savedGroups = JSON.parse(
await configRepository.getString(
`VRCX_currentUserGroups_${userId}`,
'[]'
)
);
groupStore.cachedGroups.clear();
groupStore.currentUserGroups.clear();
for (const group of savedGroups) {
const json = {
id: group.id,
name: group.name,
iconUrl: group.iconUrl,
ownerId: group.ownerId,
roles: group.roles,
myMember: {
roleIds: group.roleIds
}
};
const ref = applyGroup(json);
groupStore.currentUserGroups.set(group.id, ref);
}
if (groups) {
const promises = groups.map(async (groupId) => {
const groupRef = groupStore.cachedGroups.get(groupId);
if (
typeof groupRef !== 'undefined' &&
groupRef.roles?.length > 0
) {
return;
}
try {
console.log(`Fetching group with missing roles ${groupId}`);
const args = await groupRequest.getGroup({
groupId,
includeRoles: true
});
const ref = applyGroup(args.json);
groupStore.currentUserGroups.set(groupId, ref);
} catch (err) {
console.error(err);
}
});
await Promise.allSettled(promises);
}
groupStore.currentUserGroupsInit = true;
getCurrentUserGroups();
}
/**
*
*/
export async function getCurrentUserGroups() {
const groupStore = useGroupStore();
const userStore = useUserStore();
const args = await groupRequest.getGroups({
userId: userStore.currentUser.id
});
handleGroupList(args);
groupStore.currentUserGroups.clear();
for (const group of args.json) {
const ref = applyGroup(group);
if (!groupStore.currentUserGroups.has(group.id)) {
groupStore.currentUserGroups.set(group.id, ref);
}
}
const args1 = await groupRequest.getGroupPermissions({
userId: userStore.currentUser.id
});
handleGroupPermissions(args1);
saveCurrentUserGroups();
}
/**
*
*/
export function getCurrentUserRepresentedGroup() {
const userStore = useUserStore();
return groupRequest
.getRepresentedGroup({
userId: userStore.currentUser.id
})
.then((args) => {
handleGroupRepresented(args);
return args;
});
}
/**
*
*/
export async function initUserGroups() {
const userStore = useUserStore();
updateInGameGroupOrder();
loadCurrentUserGroups(
userStore.currentUser.id,
userStore.currentUser?.presence?.groups
);
}
/**
*
*/
export async function updateInGameGroupOrder() {
const groupStore = useGroupStore();
const gameStore = useGameStore();
const userStore = useUserStore();
groupStore.inGameGroupOrder = [];
try {
const json = await gameStore.getVRChatRegistryKey(
`VRC_GROUP_ORDER_${userStore.currentUser.id}`
);
if (!json) {
return;
}
groupStore.inGameGroupOrder = JSON.parse(json);
} catch (err) {
console.error(err);
}
}
// ─── Group actions ───────────────────────────────────────────────────────────
/**
*
* @param groupId
*/
export function leaveGroup(groupId) {
const groupStore = useGroupStore();
const userStore = useUserStore();
groupRequest
.leaveGroup({
groupId
})
.then((args) => {
const groupId = args.params.groupId;
if (
groupStore.groupDialog.visible &&
groupStore.groupDialog.id === groupId
) {
groupStore.groupDialog.inGroup = false;
getGroupDialogGroup(groupId);
}
if (
userStore.userDialog.visible &&
userStore.userDialog.id === userStore.currentUser.id &&
userStore.userDialog.representedGroup.id === groupId
) {
getCurrentUserRepresentedGroup();
}
});
}
/**
*
* @param groupId
*/
export function leaveGroupPrompt(groupId) {
const t = i18n.global.t;
const modalStore = useModalStore();
modalStore
.confirm({
description: t('confirm.leave_group'),
title: t('confirm.title')
})
.then(({ ok }) => {
if (!ok) return;
leaveGroup(groupId);
})
.catch(() => {});
}
/**
*
* @param groupId
* @param visibility
*/
export function setGroupVisibility(groupId, visibility) {
const t = i18n.global.t;
const userStore = useUserStore();
return groupRequest
.setGroupMemberProps(userStore.currentUser.id, groupId, {
visibility
})
.then((args) => {
handleGroupMemberProps(args);
toast.success(t('message.group.visibility_updated'));
return args;
});
}
/**
*
* @param groupId
* @param subscribe
*/
export function setGroupSubscription(groupId, subscribe) {
const t = i18n.global.t;
const userStore = useUserStore();
return groupRequest
.setGroupMemberProps(userStore.currentUser.id, groupId, {
isSubscribedToAnnouncements: subscribe
})
.then((args) => {
handleGroupMemberProps(args);
toast.success(t('message.group.subscription_updated'));
return args;
});
}
// ─── Event handlers ──────────────────────────────────────────────────────────
/**
*
* @param args
*/
export function handleGroupRepresented(args) {
const userStore = useUserStore();
const D = userStore.userDialog;
const json = args.json;
D.representedGroup = json;
D.representedGroup.$thumbnailUrl = convertFileUrlToImageUrl(
json.iconUrl
);
if (!json || !json.isRepresenting) {
D.isRepresentedGroupLoading = false;
}
if (!json.groupId) {
// no group
return;
}
if (args.params.userId !== userStore.currentUser.id) {
// not current user, don't apply someone elses myMember
return;
}
json.$memberId = json.id;
json.id = json.groupId;
applyGroup(json);
}
/**
*
* @param args
*/
export function handleGroupList(args) {
for (const json of args.json) {
json.$memberId = json.id;
json.id = json.groupId;
applyGroup(json);
}
}
/**
*
* @param args
*/
export function handleGroupMemberProps(args) {
const groupStore = useGroupStore();
const userStore = useUserStore();
if (args.userId === userStore.currentUser.id) {
const json = args.json;
json.$memberId = json.id;
json.id = json.groupId;
if (
groupStore.groupDialog.visible &&
groupStore.groupDialog.id === json.groupId
) {
groupStore.groupDialog.ref.myMember.visibility = json.visibility;
groupStore.groupDialog.ref.myMember.isSubscribedToAnnouncements =
json.isSubscribedToAnnouncements;
}
if (
userStore.userDialog.visible &&
userStore.userDialog.id === userStore.currentUser.id
) {
getCurrentUserRepresentedGroup();
}
handleGroupMember({
json,
params: {
groupId: json.groupId
}
});
}
let member;
if (groupStore.groupDialog.id === args.json.groupId) {
let i;
for (i = 0; i < groupStore.groupDialog.members.length; ++i) {
member = groupStore.groupDialog.members[i];
if (member.userId === args.json.userId) {
Object.assign(member, applyGroupMember(args.json));
break;
}
}
for (i = 0; i < groupStore.groupDialog.memberSearchResults.length; ++i) {
member = groupStore.groupDialog.memberSearchResults[i];
if (member.userId === args.json.userId) {
Object.assign(member, applyGroupMember(args.json));
break;
}
}
}
}
/**
*
* @param args
*/
export function handleGroupPermissions(args) {
const groupStore = useGroupStore();
const userStore = useUserStore();
if (args.params.userId !== userStore.currentUser.id) {
return;
}
const json = args.json;
for (const groupId in json) {
const permissions = json[groupId];
const group = groupStore.cachedGroups.get(groupId);
if (group) {
group.myMember.permissions = permissions;
}
}
}
/**
*
* @param args
*/
export function handleGroupMember(args) {
args.ref = applyGroupMember(args.json);
}
/**
*
* @param args
*/
export async function handleGroupUserInstances(args) {
const groupStore = useGroupStore();
const instanceStore = useInstanceStore();
groupStore.groupInstances = [];
for (const json of args.json.instances) {
if (args.json.fetchedAt) {
// tack on fetchedAt
json.$fetchedAt = args.json.fetchedAt;
}
const instanceRef = instanceStore.applyInstance(json);
const groupRef = groupStore.cachedGroups.get(json.ownerId);
if (typeof groupRef === 'undefined') {
if (watchState.isFriendsLoaded) {
const args = await groupRequest.getGroup({
groupId: json.ownerId
});
applyGroup(args.json);
}
return;
}
groupStore.groupInstances.push({
group: groupRef,
instance: instanceRef
});
}
}

View File

@@ -1,5 +1,5 @@
import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import { i18n } from '../plugin/i18n';
import { instanceRequest } from '../api';
import { parseLocation } from '../shared/utils';
@@ -16,7 +16,7 @@ export function runNewInstanceSelfInviteFlow(worldId) {
const instanceStore = useInstanceStore();
const launchStore = useLaunchStore();
const inviteStore = useInviteStore();
const { t } = useI18n();
const t = i18n.global.t;
instanceStore.createNewInstance(worldId).then((args) => {
const location = args?.json?.location;

View File

@@ -19,9 +19,6 @@ export async function runRefreshPlayerModerationsFlow() {
playerModerationRequest.getPlayerModerations(),
avatarModerationRequest.getAvatarModerations()
])
.finally(() => {
moderationStore.playerModerationTable.loading = false;
})
.then((res) => {
avatarStore.resetCachedAvatarModerations();
if (res[1]?.json) {
@@ -41,5 +38,8 @@ export async function runRefreshPlayerModerationsFlow() {
'Failed to load player/avatar moderations:',
error
);
})
.finally(() => {
moderationStore.playerModerationTable.loading = false;
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@ import { getGroupName, getWorldName, parseLocation } from '../shared/utils';
import { AppDebug } from '../service/appConfig';
import { database } from '../service/database';
import { useAvatarStore } from '../stores/avatar';
import { getAvatarName } from './avatarCoordinator';
import { useFeedStore } from '../stores/feed';
import { useFriendStore } from '../stores/friend';
import { useGeneralSettingsStore } from '../stores/settings/general';
@@ -222,7 +223,7 @@ export async function runHandleUserUpdateFlow(
avatarName: ''
};
try {
avatarInfo = await avatarStore.getAvatarName(
avatarInfo = await getAvatarName(
currentAvatarImageUrl
);
} catch (err) {
@@ -233,7 +234,7 @@ export async function runHandleUserUpdateFlow(
avatarName: ''
};
try {
previousAvatarInfo = await avatarStore.getAvatarName(
previousAvatarInfo = await getAvatarName(
previousCurrentAvatarImageUrl
);
} catch (err) {

View File

@@ -2,9 +2,11 @@ import { getWorldName, parseLocation } from '../shared/utils';
import { runUpdateFriendshipsFlow } from './friendRelationshipCoordinator';
import { useAuthStore } from '../stores/auth';
import { useAvatarStore } from '../stores/avatar';
import { addAvatarToHistory, addAvatarWearTime } from './avatarCoordinator';
import { useFriendStore } from '../stores/friend';
import { useGameStore } from '../stores/game';
import { useGroupStore } from '../stores/group';
import { applyPresenceGroups } from './groupCoordinator';
import { useInstanceStore } from '../stores/instance';
import { useUserStore } from '../stores/user';
@@ -28,9 +30,9 @@ export function runAvatarSwapFlow(
return;
}
if (json.currentAvatar !== ref.currentAvatar) {
avatarStore.addAvatarToHistory(json.currentAvatar);
addAvatarToHistory(json.currentAvatar);
if (gameStore.isGameRunning) {
avatarStore.addAvatarWearTime(ref.currentAvatar);
addAvatarWearTime(ref.currentAvatar);
ref.$previousAvatarSwapTime = now();
}
}
@@ -64,7 +66,7 @@ export function runPostApplySyncFlow(ref) {
const instanceStore = useInstanceStore();
const friendStore = useFriendStore();
groupStore.applyPresenceGroups(ref);
applyPresenceGroups(ref);
instanceStore.applyQueuedInstance(ref.queuedInstance);
friendStore.updateUserCurrentStatus(ref);
if (typeof ref.friends !== 'undefined') {

View File

@@ -0,0 +1,246 @@
import { nextTick } from 'vue';
import { toast } from 'vue-sonner';
import { i18n } from '../plugin/i18n';
import {
checkVRChatCache,
createDefaultWorldRef,
evictMapCache,
getAvailablePlatforms,
getBundleDateSize,
getWorldMemo,
isRealInstance,
parseLocation,
sanitizeEntityJson
} from '../shared/utils';
import { instanceRequest, queryRequest, worldRequest } from '../api';
import { database } from '../service/database';
import { patchWorldFromEvent } from '../queries';
import { processBulk } from '../service/request';
import { applyFavorite } from './favoriteCoordinator';
import { useFavoriteStore } from '../stores/favorite';
import { useInstanceStore } from '../stores/instance';
import { useLocationStore } from '../stores/location';
import { useUiStore } from '../stores/ui';
import { useUserStore } from '../stores/user';
import { useWorldStore } from '../stores/world';
/**
* @param {string} tag
* @param {string} shortName
* @param options
*/
export function showWorldDialog(tag, shortName = null, options = {}) {
const worldStore = useWorldStore();
const uiStore = useUiStore();
const instanceStore = useInstanceStore();
const favoriteStore = useFavoriteStore();
const locationStore = useLocationStore();
const t = i18n.global.t;
const D = worldStore.worldDialog;
const forceRefresh = Boolean(options?.forceRefresh);
const L = parseLocation(tag);
if (L.worldId === '') {
return;
}
const isMainDialogOpen = uiStore.openDialog({
type: 'world',
id: L.worldId,
tag,
shortName
});
D.visible = true;
if (isMainDialogOpen && D.id === L.worldId && !forceRefresh) {
uiStore.setDialogCrumbLabel('world', D.id, D.ref?.name || D.id);
instanceStore.applyWorldDialogInstances();
nextTick(() => (D.loading = false));
return;
}
L.shortName = shortName;
D.id = L.worldId;
D.$location = L;
D.loading = true;
D.inCache = false;
D.cacheSize = '';
D.cacheLocked = false;
D.cachePath = '';
D.fileAnalysis = {};
D.rooms = [];
D.lastVisit = '';
D.visitCount = 0;
D.timeSpent = 0;
D.isFavorite = false;
D.avatarScalingDisabled = false;
D.focusViewDisabled = false;
D.isPC = false;
D.isQuest = false;
D.isIos = false;
D.hasPersistData = false;
D.memo = '';
const LL = parseLocation(locationStore.lastLocation.location);
let currentWorldMatch = false;
if (LL.worldId === D.id) {
currentWorldMatch = true;
}
getWorldMemo(D.id).then((memo) => {
if (memo.worldId === D.id) {
D.memo = memo.memo;
}
});
database.getLastVisit(D.id, currentWorldMatch).then((ref) => {
if (ref.worldId === D.id) {
D.lastVisit = ref.created_at;
}
});
database.getVisitCount(D.id).then((ref) => {
if (ref.worldId === D.id) {
D.visitCount = ref.visitCount;
}
});
database.getTimeSpentInWorld(D.id).then((ref) => {
if (ref.worldId === D.id) {
D.timeSpent = ref.timeSpent;
}
});
const loadWorldRequest = worldRequest.getWorld({
worldId: L.worldId
});
loadWorldRequest
.catch((err) => {
nextTick(() => (D.loading = false));
D.id = null;
D.visible = false;
uiStore.jumpBackDialogCrumb();
toast.error(t('message.world.load_failed'));
throw err;
})
.then((args) => {
if (D.id === args.ref.id) {
D.ref = args.ref;
uiStore.setDialogCrumbLabel(
'world',
D.id,
D.ref?.name || D.id
);
D.visible = true;
D.loading = false;
D.isFavorite = favoriteStore.getCachedFavoritesByObjectId(
D.id
);
if (!D.isFavorite) {
D.isFavorite =
favoriteStore.localWorldFavoritesList.includes(
D.id
);
}
let { isPC, isQuest, isIos } = getAvailablePlatforms(
args.ref.unityPackages
);
D.avatarScalingDisabled = args.ref?.tags.includes(
'feature_avatar_scaling_disabled'
);
D.focusViewDisabled = args.ref?.tags.includes(
'feature_focus_view_disabled'
);
D.isPC = isPC;
D.isQuest = isQuest;
D.isIos = isIos;
worldStore.updateVRChatWorldCache();
queryRequest
.fetch('worldPersistData', {
worldId: D.id
})
.then((args) => {
if (
args.params.worldId === worldStore.worldDialog.id &&
worldStore.worldDialog.visible
) {
worldStore.worldDialog.hasPersistData =
args.json !== false;
}
});
}
});
}
/**
* @param {object} json
* @returns {object} ref
*/
export function applyWorld(json) {
const worldStore = useWorldStore();
const favoriteStore = useFavoriteStore();
const instanceStore = useInstanceStore();
const userStore = useUserStore();
sanitizeEntityJson(json, ['name', 'description']);
let ref = worldStore.cachedWorlds.get(json.id);
if (typeof ref === 'undefined') {
ref = createDefaultWorldRef(json);
evictMapCache(worldStore.cachedWorlds, 10000, () => false, {
logLabel: 'World cache cleanup'
});
worldStore.cachedWorlds.set(ref.id, ref);
} else {
Object.assign(ref, json);
}
ref.$isLabs = ref.tags.includes('system_labs');
applyFavorite('world', ref.id);
const userDialog = userStore.userDialog;
if (userDialog.visible && userDialog.$location.worldId === ref.id) {
userStore.applyUserDialogLocation();
}
const worldDialog = worldStore.worldDialog;
if (worldDialog.visible && worldDialog.id === ref.id) {
worldDialog.ref = ref;
worldDialog.avatarScalingDisabled = ref.tags?.includes(
'feature_avatar_scaling_disabled'
);
worldDialog.focusViewDisabled = ref.tags?.includes(
'feature_focus_view_disabled'
);
instanceStore.applyWorldDialogInstances();
for (const room of worldDialog.rooms) {
if (isRealInstance(room.tag)) {
instanceRequest.getInstance({
worldId: worldDialog.id,
instanceId: room.id
});
}
}
if (Object.keys(worldDialog.fileAnalysis).length === 0) {
getBundleDateSize(ref);
}
}
if (favoriteStore.localWorldFavoritesList.includes(ref.id)) {
// update db cache
database.addWorldToCache(ref);
}
patchWorldFromEvent(ref);
return ref;
}
/**
* Preload all own worlds into cache at startup for global search.
*/
export 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);
}
}
});
}