mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-18 22:33:50 +02:00
refactor store
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
649
src/coordinators/avatarCoordinator.js
Normal file
649
src/coordinators/avatarCoordinator.js
Normal 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
957
src/coordinators/groupCoordinator.js
Normal file
957
src/coordinators/groupCoordinator.js
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
1070
src/coordinators/userCoordinator.js
Normal file
1070
src/coordinators/userCoordinator.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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) {
|
||||
|
||||
@@ -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') {
|
||||
|
||||
246
src/coordinators/worldCoordinator.js
Normal file
246
src/coordinators/worldCoordinator.js
Normal 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user