mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-26 02:03:49 +02:00
835 lines
26 KiB
JavaScript
835 lines
26 KiB
JavaScript
import { nextTick, ref, watch } from 'vue';
|
|
import { defineStore } from 'pinia';
|
|
import { toast } from 'vue-sonner';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
import {
|
|
checkVRChatCache,
|
|
extractFileId,
|
|
getAvailablePlatforms,
|
|
getBundleDateSize,
|
|
getPlatformInfo,
|
|
replaceBioSymbols,
|
|
storeAvatarImage
|
|
} from '../shared/utils';
|
|
import { avatarRequest, miscRequest } from '../api';
|
|
import { patchAvatarFromEvent } from '../query';
|
|
import { AppDebug } from '../service/appConfig';
|
|
import { database } from '../service/database';
|
|
import { processBulk } from '../service/request';
|
|
import { useAdvancedSettingsStore } from './settings/advanced';
|
|
import { useAvatarProviderStore } from './avatarProvider';
|
|
import { useFavoriteStore } from './favorite';
|
|
import { useModalStore } from './modal';
|
|
import { useUiStore } from './ui';
|
|
import { useUserStore } from './user';
|
|
import { useVRCXUpdaterStore } from './vrcxUpdater';
|
|
import { watchState } from '../service/watchState';
|
|
|
|
import webApiService from '../service/webapi';
|
|
|
|
export const useAvatarStore = defineStore('Avatar', () => {
|
|
const favoriteStore = useFavoriteStore();
|
|
const avatarProviderStore = useAvatarProviderStore();
|
|
const vrcxUpdaterStore = useVRCXUpdaterStore();
|
|
const advancedSettingsStore = useAdvancedSettingsStore();
|
|
const userStore = useUserStore();
|
|
const modalStore = useModalStore();
|
|
const uiStore = useUiStore();
|
|
const { t } = useI18n();
|
|
|
|
let cachedAvatarModerations = new Map();
|
|
let cachedAvatars = new Map();
|
|
let cachedAvatarNames = new Map();
|
|
|
|
const avatarDialog = ref({
|
|
visible: false,
|
|
loading: false,
|
|
activeTab: 'Info',
|
|
lastActiveTab: 'Info',
|
|
id: '',
|
|
memo: '',
|
|
ref: {},
|
|
isFavorite: false,
|
|
isBlocked: false,
|
|
isQuestFallback: false,
|
|
hasImposter: false,
|
|
imposterVersion: '',
|
|
isPC: false,
|
|
isQuest: false,
|
|
isIos: false,
|
|
platformInfo: {},
|
|
galleryImages: [],
|
|
galleryLoading: false,
|
|
inCache: false,
|
|
cacheSize: '',
|
|
cacheLocked: false,
|
|
cachePath: '',
|
|
fileAnalysis: {},
|
|
timeSpent: 0
|
|
});
|
|
const avatarHistory = ref([]);
|
|
const loadingToastId = ref(null);
|
|
|
|
watch(
|
|
() => watchState.isLoggedIn,
|
|
(isLoggedIn) => {
|
|
avatarDialog.value.visible = false;
|
|
cachedAvatars.clear();
|
|
cachedAvatarNames.clear();
|
|
cachedAvatarModerations.clear();
|
|
avatarHistory.value = [];
|
|
if (isLoggedIn) {
|
|
getAvatarHistory();
|
|
preloadOwnAvatars();
|
|
}
|
|
},
|
|
{ flush: 'sync' }
|
|
);
|
|
|
|
/**
|
|
* @param {object} json
|
|
* @returns {object} ref
|
|
*/
|
|
function applyAvatar(json) {
|
|
json.name = replaceBioSymbols(json.name);
|
|
json.description = replaceBioSymbols(json.description);
|
|
let ref = cachedAvatars.get(json.id);
|
|
if (typeof ref === 'undefined') {
|
|
ref = {
|
|
acknowledgements: '',
|
|
authorId: '',
|
|
authorName: '',
|
|
created_at: '',
|
|
description: '',
|
|
featured: false,
|
|
highestPrice: null,
|
|
id: '',
|
|
imageUrl: '',
|
|
listingDate: null,
|
|
lock: false,
|
|
lowestPrice: null,
|
|
name: '',
|
|
pendingUpload: false,
|
|
performance: {},
|
|
productId: null,
|
|
publishedListings: [],
|
|
releaseStatus: '',
|
|
searchable: false,
|
|
styles: [],
|
|
tags: [],
|
|
thumbnailImageUrl: '',
|
|
unityPackageUrl: '',
|
|
unityPackageUrlObject: {},
|
|
unityPackages: [],
|
|
updated_at: '',
|
|
version: 0,
|
|
...json
|
|
};
|
|
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);
|
|
}
|
|
favoriteStore.applyFavorite('avatar', ref.id);
|
|
if (favoriteStore.localAvatarFavoritesList.includes(ref.id)) {
|
|
const avatarRef = ref;
|
|
for (
|
|
let i = 0;
|
|
i < favoriteStore.localAvatarFavoriteGroups.length;
|
|
++i
|
|
) {
|
|
const groupName = favoriteStore.localAvatarFavoriteGroups[i];
|
|
if (!favoriteStore.localAvatarFavorites[groupName]) {
|
|
continue;
|
|
}
|
|
for (
|
|
let j = 0;
|
|
j < favoriteStore.localAvatarFavorites[groupName].length;
|
|
++j
|
|
) {
|
|
const favoriteRef =
|
|
favoriteStore.localAvatarFavorites[groupName][j];
|
|
if (favoriteRef.id === avatarRef.id) {
|
|
favoriteStore.localAvatarFavorites[groupName][j] =
|
|
avatarRef;
|
|
}
|
|
}
|
|
}
|
|
|
|
// update db cache
|
|
database.addAvatarToCache(avatarRef);
|
|
}
|
|
patchAvatarFromEvent(ref);
|
|
return ref;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} avatarId
|
|
* @returns
|
|
*/
|
|
function showAvatarDialog(avatarId, options = {}) {
|
|
const D = avatarDialog.value;
|
|
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 = cachedAvatarModerations.has(avatarId);
|
|
const ref2 = 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 })
|
|
: avatarRequest.getCachedAvatar({ avatarId });
|
|
loadAvatarRequest
|
|
.then((args) => {
|
|
const ref = applyAvatar(args.json);
|
|
D.ref = ref;
|
|
uiStore.setDialogCrumbLabel(
|
|
'avatar',
|
|
D.id,
|
|
D.ref?.name || D.id
|
|
);
|
|
getAvatarGallery(avatarId);
|
|
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));
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} avatarId
|
|
* @returns {Promise<string[]>}
|
|
*/
|
|
async function getAvatarGallery(avatarId) {
|
|
const D = avatarDialog.value;
|
|
const args = await avatarRequest
|
|
.getAvatarGallery(avatarId)
|
|
.finally(() => {
|
|
D.galleryLoading = false;
|
|
});
|
|
if (args.params.galleryId !== D.id) {
|
|
return;
|
|
}
|
|
D.galleryImages = [];
|
|
// wtf is this? why is order sorting only needed if it's your own avatar?
|
|
const sortedGallery = args.json.sort((a, b) => {
|
|
if (!a.order && !b.order) {
|
|
return 0;
|
|
}
|
|
return a.order - b.order;
|
|
});
|
|
for (const file of sortedGallery) {
|
|
const url = file.versions[file.versions.length - 1].file.url;
|
|
D.galleryImages.push(url);
|
|
}
|
|
|
|
// for JSON tab treeData
|
|
D.ref.gallery = args.json;
|
|
return D.galleryImages;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {object} json
|
|
* @returns {object} ref
|
|
*/
|
|
function applyAvatarModeration(json) {
|
|
// fix inconsistent Unix time response
|
|
if (typeof json.created === 'number') {
|
|
json.created = new Date(json.created).toJSON();
|
|
}
|
|
|
|
let ref = cachedAvatarModerations.get(json.targetAvatarId);
|
|
if (typeof ref === 'undefined') {
|
|
ref = {
|
|
avatarModerationType: '',
|
|
created: '',
|
|
targetAvatarId: '',
|
|
...json
|
|
};
|
|
cachedAvatarModerations.set(ref.targetAvatarId, ref);
|
|
} else {
|
|
Object.assign(ref, json);
|
|
}
|
|
|
|
// update avatar dialog
|
|
const D = avatarDialog.value;
|
|
if (
|
|
D.visible &&
|
|
ref.avatarModerationType === 'block' &&
|
|
D.id === ref.targetAvatarId
|
|
) {
|
|
D.isBlocked = true;
|
|
}
|
|
|
|
return ref;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function updateVRChatAvatarCache() {
|
|
const D = avatarDialog.value;
|
|
if (D.visible) {
|
|
D.inCache = false;
|
|
D.cacheSize = '';
|
|
D.cacheLocked = false;
|
|
D.cachePath = '';
|
|
checkVRChatCache(D.ref).then((cacheInfo) => {
|
|
if (cacheInfo.Item1 > 0) {
|
|
D.inCache = true;
|
|
D.cacheSize = `${(cacheInfo.Item1 / 1048576).toFixed(2)} MB`;
|
|
D.cachePath = cacheInfo.Item3;
|
|
}
|
|
D.cacheLocked = cacheInfo.Item2;
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async function getAvatarHistory() {
|
|
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);
|
|
}
|
|
avatarHistory.value = historyArray;
|
|
}
|
|
|
|
/**
|
|
* @param {string} avatarId
|
|
*/
|
|
function addAvatarToHistory(avatarId) {
|
|
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 = avatarHistory.value;
|
|
for (let i = 0; i < historyArray.length; ++i) {
|
|
if (historyArray[i].id === ref.id) {
|
|
historyArray.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
avatarHistory.value.unshift(ref);
|
|
})
|
|
.catch((err) => {
|
|
console.error('Failed to add avatar to history:', err);
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function clearAvatarHistory() {
|
|
avatarHistory.value = [];
|
|
database.clearAvatarHistory();
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function promptClearAvatarHistory() {
|
|
modalStore
|
|
.confirm({
|
|
description: t('confirm.clear_avatar_history'),
|
|
title: 'Confirm'
|
|
})
|
|
.then(({ ok }) => {
|
|
if (!ok) return;
|
|
clearAvatarHistory();
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} imageUrl
|
|
* @returns {Promise<object>}
|
|
*/
|
|
async function getAvatarName(imageUrl) {
|
|
const fileId = extractFileId(imageUrl);
|
|
if (!fileId) {
|
|
return {
|
|
ownerId: '',
|
|
avatarName: '-'
|
|
};
|
|
}
|
|
if (cachedAvatarNames.has(fileId)) {
|
|
return cachedAvatarNames.get(fileId);
|
|
}
|
|
try {
|
|
const args = await miscRequest.getFile({ fileId });
|
|
return storeAvatarImage(args, cachedAvatarNames);
|
|
} catch (error) {
|
|
console.error('Failed to get avatar images:', error);
|
|
return {
|
|
ownerId: '',
|
|
avatarName: '-'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param type
|
|
* @param search
|
|
*/
|
|
async function lookupAvatars(type, search) {
|
|
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
|
|
*/
|
|
async function lookupAvatarByImageFileId(authorId, fileId) {
|
|
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) {
|
|
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 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
|
|
*/
|
|
function selectAvatarWithConfirmation(id) {
|
|
modalStore
|
|
.confirm({
|
|
description: t('confirm.select_avatar'),
|
|
title: 'Confirm'
|
|
})
|
|
.then(({ ok }) => {
|
|
if (!ok) return;
|
|
selectAvatarWithoutConfirmation(id);
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param id
|
|
*/
|
|
async function selectAvatarWithoutConfirmation(id) {
|
|
if (userStore.currentUser.currentAvatar === id) {
|
|
toast.info('Avatar already selected');
|
|
return;
|
|
}
|
|
return avatarRequest
|
|
.selectAvatar({
|
|
avatarId: id
|
|
})
|
|
.then(() => {
|
|
toast.success('Avatar changed');
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param fileId
|
|
*/
|
|
function checkAvatarCache(fileId) {
|
|
let avatarId = '';
|
|
for (let ref of cachedAvatars.values()) {
|
|
if (extractFileId(ref.imageUrl) === fileId) {
|
|
avatarId = ref.id;
|
|
}
|
|
}
|
|
return avatarId;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param fileId
|
|
* @param ownerUserId
|
|
*/
|
|
async function checkAvatarCacheRemote(fileId, ownerUserId) {
|
|
if (advancedSettingsStore.avatarRemoteDatabase) {
|
|
try {
|
|
toast.dismiss(loadingToastId.value);
|
|
loadingToastId.value = 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(loadingToastId.value);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param refUserId
|
|
* @param ownerUserId
|
|
* @param currentAvatarImageUrl
|
|
*/
|
|
async function showAvatarAuthorDialog(
|
|
refUserId,
|
|
ownerUserId,
|
|
currentAvatarImageUrl
|
|
) {
|
|
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 userStore.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'));
|
|
userStore.showUserDialog(avatarInfo.ownerId);
|
|
}
|
|
}
|
|
if (avatarId) {
|
|
showAvatarDialog(avatarId);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param avatarId
|
|
*/
|
|
function addAvatarWearTime(avatarId) {
|
|
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.
|
|
*/
|
|
async function preloadOwnAvatars() {
|
|
const params = {
|
|
n: 50,
|
|
offset: 0,
|
|
sort: 'updated',
|
|
order: 'descending',
|
|
releaseStatus: 'all',
|
|
user: 'me'
|
|
};
|
|
await processBulk({
|
|
fn: avatarRequest.getAvatars,
|
|
N: -1,
|
|
params,
|
|
handle: (args) => {
|
|
for (const json of args.json) {
|
|
applyAvatar(json);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
return {
|
|
avatarDialog,
|
|
avatarHistory,
|
|
cachedAvatarModerations,
|
|
cachedAvatars,
|
|
cachedAvatarNames,
|
|
|
|
showAvatarDialog,
|
|
applyAvatarModeration,
|
|
getAvatarGallery,
|
|
updateVRChatAvatarCache,
|
|
getAvatarHistory,
|
|
addAvatarToHistory,
|
|
applyAvatar,
|
|
promptClearAvatarHistory,
|
|
getAvatarName,
|
|
lookupAvatars,
|
|
selectAvatarWithConfirmation,
|
|
selectAvatarWithoutConfirmation,
|
|
showAvatarAuthorDialog,
|
|
addAvatarWearTime,
|
|
preloadOwnAvatars
|
|
};
|
|
});
|