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 { request } from '../service/request';
import { useUserStore } from '../stores';
import { handleConfig } from '../coordinators/userCoordinator';
const loginReq = {
/**
@@ -63,7 +63,7 @@ const loginReq = {
const args = {
json
};
useUserStore().handleConfig(args);
handleConfig(args);
return args;
});
}

View File

@@ -1,6 +1,7 @@
import { patchAndRefetchActiveQuery, queryKeys } from '../queries';
import { request } from '../service/request';
import { useUserStore } from '../stores';
import { applyCurrentUser } from '../coordinators/userCoordinator';
const avatarReq = {
/**
@@ -65,7 +66,7 @@ const avatarReq = {
* @returns {Promise<{json: any, params}>}
*/
selectAvatar(params) {
const userStore = useUserStore();
return request(`avatars/${params.avatarId}/select`, {
method: 'PUT',
params
@@ -74,7 +75,7 @@ const avatarReq = {
json,
params
};
const ref = userStore.applyCurrentUser(json);
const ref = applyCurrentUser(json);
patchAndRefetchActiveQuery({
queryKey: queryKeys.user(ref.id),
nextData: {
@@ -97,7 +98,7 @@ const avatarReq = {
* @returns { Promise<{json: any, params}> }
*/
selectFallbackAvatar(params) {
const userStore = useUserStore();
return request(`avatars/${params.avatarId}/selectfallback`, {
method: 'PUT',
params
@@ -106,7 +107,7 @@ const avatarReq = {
json,
params
};
const ref = userStore.applyCurrentUser(json);
const ref = applyCurrentUser(json);
patchAndRefetchActiveQuery({
queryKey: queryKeys.user(ref.id),
nextData: {

View File

@@ -1,6 +1,7 @@
import { queryClient } from '../queries';
import { request } from '../service/request';
import { useUserStore } from '../stores/user';
import { applyUser } from '../coordinators/userCoordinator';
/**
*
@@ -22,7 +23,7 @@ const friendReq = {
* @type {import('../types/api/friend').GetFriends}
*/
getFriends(params) {
const userStore = useUserStore();
return request('auth/user/friends', {
method: 'GET',
params
@@ -36,7 +37,7 @@ const friendReq = {
console.error('/friends gave us garbage', user);
continue;
}
userStore.applyUser(user);
applyUser(user);
}
return args;
});

View File

@@ -1,4 +1,5 @@
import { useGroupStore, useUserStore } from '../stores';
import { useUserStore } from '../stores';
import { applyGroup } from '../coordinators/groupCoordinator';
import { queryClient } from '../queries';
import { request } from '../service/request';
@@ -89,12 +90,11 @@ const groupReq = {
includeRoles: params.includeRoles || false
}
}).then((json) => {
const groupStore = useGroupStore();
const args = {
json,
params
};
args.ref = groupStore.applyGroup(json);
args.ref = applyGroup(json);
return args;
});
},

View File

@@ -1,4 +1,5 @@
import { useAvatarStore, useWorldStore } from '../stores';
import { applyWorld } from '../coordinators/worldCoordinator';
import { request } from '../service/request';
const imageReq = {
@@ -267,7 +268,7 @@ const imageReq = {
json,
params
};
args.ref = worldStore.applyWorld(json);
args.ref = applyWorld(json);
return args;
});
},

View File

@@ -1,6 +1,7 @@
import { patchAndRefetchActiveQuery, queryKeys } from '../queries';
import { request } from '../service/request';
import { useUserStore } from '../stores';
import { applyUser, applyCurrentUser } from '../coordinators/userCoordinator';
/**
* @returns {string}
@@ -16,7 +17,7 @@ const userReq = {
* @type {import('../types/api/user').GetUser}
*/
getUser(params) {
const userStore = useUserStore();
return request(`users/${params.userId}`, {
method: 'GET'
}).then((json) => {
@@ -29,7 +30,7 @@ const userReq = {
const args = {
json,
params,
ref: userStore.applyUser(json)
ref: applyUser(json)
};
return args;
});
@@ -56,7 +57,7 @@ const userReq = {
* @returns {Promise<{json: any, params: {tags: string[]}}>}
*/
addUserTags(params) {
const userStore = useUserStore();
return request(`users/${getCurrentUserId()}/addTags`, {
method: 'POST',
params
@@ -65,7 +66,7 @@ const userReq = {
json,
params
};
userStore.applyCurrentUser(json);
applyCurrentUser(json);
return args;
});
},
@@ -75,7 +76,7 @@ const userReq = {
* @returns {Promise<{json: any, params: {tags: string[]}}>}
*/
removeUserTags(params) {
const userStore = useUserStore();
return request(`users/${getCurrentUserId()}/removeTags`, {
method: 'POST',
params
@@ -84,7 +85,7 @@ const userReq = {
json,
params
};
userStore.applyCurrentUser(json);
applyCurrentUser(json);
return args;
});
},
@@ -113,7 +114,7 @@ const userReq = {
* @type {import('../types/api/user').GetCurrentUser}
*/
saveCurrentUser(params) {
const userStore = useUserStore();
return request(`users/${getCurrentUserId()}`, {
method: 'PUT',
params
@@ -121,7 +122,7 @@ const userReq = {
const args = {
json,
params,
ref: userStore.applyCurrentUser(json)
ref: applyCurrentUser(json)
};
patchAndRefetchActiveQuery({
queryKey: queryKeys.user(args.ref.id),

View File

@@ -1,13 +1,12 @@
import { patchAndRefetchActiveQuery, queryKeys } from '../queries';
import { request } from '../service/request';
import { useWorldStore } from '../stores';
import { applyWorld } from '../coordinators/worldCoordinator';
const worldReq = {
/**
* @type {import('../types/api/world').GetWorld}
*/
getWorld(params) {
const worldStore = useWorldStore();
return request(`worlds/${params.worldId}`, {
method: 'GET'
}).then((json) => {
@@ -15,7 +14,7 @@ const worldReq = {
json,
params
};
args.ref = worldStore.applyWorld(json);
args.ref = applyWorld(json);
return args;
});
},
@@ -24,7 +23,6 @@ const worldReq = {
* @type {import('../types/api/world').GetWorlds}
*/
getWorlds(params, option) {
const worldStore = useWorldStore();
let endpoint = 'worlds';
if (typeof option !== 'undefined') {
endpoint = `worlds/${option}`;
@@ -39,7 +37,7 @@ const worldReq = {
option
};
for (const json of args.json) {
worldStore.applyWorld(json);
applyWorld(json);
}
return args;
});
@@ -64,7 +62,6 @@ const worldReq = {
* @type {import('../types/api/world').SaveWorld}
*/
saveWorld(params) {
const worldStore = useWorldStore();
return request(`worlds/${params.id}`, {
method: 'PUT',
params
@@ -73,7 +70,7 @@ const worldReq = {
json,
params
};
args.ref = worldStore.applyWorld(json);
args.ref = applyWorld(json);
patchAndRefetchActiveQuery({
queryKey: queryKeys.world(args.ref.id),
nextData: args
@@ -92,7 +89,6 @@ const worldReq = {
* @returns {Promise<{json: any, params}>}
*/
publishWorld(params) {
const worldStore = useWorldStore();
return request(`worlds/${params.worldId}/publish`, {
method: 'PUT',
params
@@ -101,7 +97,7 @@ const worldReq = {
json,
params
};
args.ref = worldStore.applyWorld(json);
args.ref = applyWorld(json);
patchAndRefetchActiveQuery({
queryKey: queryKeys.world(args.ref.id),
nextData: args
@@ -120,7 +116,6 @@ const worldReq = {
* @returns {Promise<{json: any, params}>}
*/
unpublishWorld(params) {
const worldStore = useWorldStore();
return request(`worlds/${params.worldId}/publish`, {
method: 'DELETE',
params
@@ -129,7 +124,7 @@ const worldReq = {
json,
params
};
args.ref = worldStore.applyWorld(json);
args.ref = applyWorld(json);
patchAndRefetchActiveQuery({
queryKey: queryKeys.world(args.ref.id),
nextData: args

View File

@@ -22,6 +22,7 @@
import { TooltipWrapper } from './ui/tooltip';
import { useAvatarStore } from '../stores';
import { getAvatarName, showAvatarAuthorDialog } from '../coordinators/avatarCoordinator';
const { t } = useI18n();
const avatarStore = useAvatarStore();
@@ -54,7 +55,7 @@
ownerId = props.hintownerid;
} else {
try {
const info = await avatarStore.getAvatarName(props.imageurl);
const info = await getAvatarName(props.imageurl);
avatarName.value = info.avatarName;
ownerId = info.ownerId;
} catch {
@@ -77,7 +78,7 @@
const confirm = () => {
if (!props.imageurl) return;
avatarStore.showAvatarAuthorDialog(props.userid, ownerId, props.imageurl);
showAvatarAuthorDialog(props.userid, ownerId, props.imageurl);
};
watch([() => props.imageurl, () => props.userid, () => props.avatartags], parse, { immediate: true });

View File

@@ -1,14 +1,12 @@
<template>
<span @click="showUserDialog" class="cursor-pointer">{{ username }}</span>
<span @click="openUserDialog" class="cursor-pointer">{{ username }}</span>
</template>
<script setup>
import { ref, watch } from 'vue';
import { queryRequest } from '../api';
import { useUserStore } from '../stores';
const userStore = useUserStore();
import { showUserDialog } from '../coordinators/userCoordinator';
const props = defineProps({
userid: String,
@@ -40,9 +38,10 @@
/**
*
*/
function showUserDialog() {
userStore.showUserDialog(props.userid);
function openUserDialog() {
showUserDialog(props.userid);
}
watch([() => props.userid, () => props.location, () => props.forceUpdateKey], parse, { immediate: true });
</script>

View File

@@ -161,6 +161,7 @@
} from '../stores';
import { checkCanInviteSelf, formatDateFilter, hasGroupPermission, parseLocation } from '../shared/utils';
import { instanceRequest, miscRequest } from '../api';
import { showUserDialog } from '../coordinators/userCoordinator';
defineOptions({
inheritAttrs: false
@@ -352,9 +353,7 @@
}
};
const showUserDialog = (userId) => {
userStore.showUserDialog(userId);
};
const closeInstance = (location) => {
modalStore

View File

@@ -50,12 +50,13 @@
useSearchStore,
useWorldStore
} from '../stores';
import { showWorldDialog } from '../coordinators/worldCoordinator';
import { Spinner } from './ui/spinner';
import { accessTypeLocaleKeyMap } from '../shared/constants';
const { t } = useI18n();
const { cachedWorlds, showWorldDialog } = useWorldStore();
const { cachedWorlds } = useWorldStore();
const { showGroupDialog } = useGroupStore();
const { showPreviousInstancesInfoDialog } = useInstanceStore();
const { verifyShortName } = useSearchStore();

View File

@@ -5,7 +5,7 @@
<Unlock v-if="isUnlocked" :class="['inline-block', 'mr-1.25']" />
<span> {{ accessTypeName }} #{{ instanceName }}</span>
</span>
<span v-if="groupName" @click="showGroupDialog" class="cursor-pointer">({{ groupName }})</span>
<span v-if="groupName" @click="openLocationGroupDialog" class="cursor-pointer">({{ groupName }})</span>
<TooltipWrapper v-if="isClosed" :content="t('dialog.user.info.instance_closed')">
<AlertTriangle :class="['inline-block', 'ml-1']" style="color: lightcoral" />
</TooltipWrapper>
@@ -20,6 +20,7 @@
import { useI18n } from 'vue-i18n';
import { useGroupStore, useInstanceStore, useLaunchStore } from '../stores';
import { showGroupDialog } from '../coordinators/groupCoordinator';
import { getGroupName, parseLocation } from '../shared/utils';
import { accessTypeLocaleKeyMap } from '../shared/constants';
@@ -135,10 +136,10 @@
/**
*
*/
function showGroupDialog() {
function openLocationGroupDialog() {
if (!location.value) return;
const L = parseLocation(location.value);
if (!L.groupId) return;
groupStore.showGroupDialog(L.groupId);
showGroupDialog(L.groupId);
}
</script>

View File

@@ -601,20 +601,22 @@
import { formatJsonVars } from '../../../shared/utils/base/ui';
import { handleImageUploadInput } from '../../../shared/utils/imageUpload';
import { runDeleteVRChatCacheFlow as deleteVRChatCache } from '../../../coordinators/gameCoordinator';
import { showAvatarDialog, applyAvatar, selectAvatarWithoutConfirmation } from '../../../coordinators/avatarCoordinator';
import { useAvatarDialogCommands } from './useAvatarDialogCommands';
import DialogJsonTab from '../DialogJsonTab.vue';
import ImageCropDialog from '../ImageCropDialog.vue';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const SetAvatarStylesDialog = defineAsyncComponent(() => import('./SetAvatarStylesDialog.vue'));
const SetAvatarTagsDialog = defineAsyncComponent(() => import('./SetAvatarTagsDialog.vue'));
const { showUserDialog, sortUserDialogAvatars } = useUserStore();
const { sortUserDialogAvatars } = useUserStore();
const { userDialog, currentUser } = storeToRefs(useUserStore());
const avatarStore = useAvatarStore();
const { cachedAvatarModerations, cachedAvatars } = avatarStore;
const { avatarDialog } = storeToRefs(avatarStore);
const { showAvatarDialog, getAvatarGallery, applyAvatarModeration, applyAvatar, selectAvatarWithoutConfirmation } =
const { getAvatarGallery, applyAvatarModeration } =
avatarStore;
const { showFavoriteDialog } = useFavoriteStore();
const { isGameRunning } = storeToRefs(useGameStore());

View File

@@ -93,6 +93,7 @@
import { avatarRequest, queryRequest } from '../../../api';
import { arraysMatch } from '../../../shared/utils';
import { useAvatarStore } from '../../../stores';
import { applyAvatar } from '../../../coordinators/avatarCoordinator';
const props = defineProps({
setAvatarStylesDialog: {
@@ -104,7 +105,6 @@
const emit = defineEmits(['update:setAvatarStylesDialog']);
const { t } = useI18n();
const { applyAvatar } = useAvatarStore();
const SELECT_CLEAR_VALUE = '__clear__';

View File

@@ -128,8 +128,8 @@
import { avatarRequest } from '../../../api';
import { removeFromArray } from '../../../shared/utils';
import { useAvatarStore } from '../../../stores';
import { showAvatarDialog, applyAvatar } from '../../../coordinators/avatarCoordinator';
const { showAvatarDialog, applyAvatar } = useAvatarStore();
const { cachedAvatars } = useAvatarStore();
const { t } = useI18n();

View File

@@ -407,6 +407,13 @@
removeFromArray
} from '../../../shared/utils';
import { useGalleryStore, useGroupStore, useModalStore, useUserStore } from '../../../stores';
import {
getGroupDialogGroup,
showGroupDialog,
leaveGroupPrompt,
setGroupVisibility,
setGroupSubscription
} from '../../../coordinators/groupCoordinator';
import { groupRequest, queryRequest } from '../../../api';
import { queryKeys, refetchActiveEntityQuery } from '../../../queries';
import { Badge } from '../../ui/badge';
@@ -419,6 +426,7 @@
import GroupDialogPhotosTab from './GroupDialogPhotosTab.vue';
import GroupDialogPostsTab from './GroupDialogPostsTab.vue';
import GroupPostEditDialog from './GroupPostEditDialog.vue';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const { t } = useI18n();
const groupDialogTabs = computed(() => [
@@ -431,16 +439,11 @@
const modalStore = useModalStore();
const { showUserDialog } = useUserStore();
const { currentUser } = storeToRefs(useUserStore());
const { groupDialog, inviteGroupDialog } = storeToRefs(useGroupStore());
const {
getGroupDialogGroup,
updateGroupPostSearch,
showGroupDialog,
leaveGroupPrompt,
setGroupVisibility,
setGroupSubscription,
showGroupMemberModerationDialog
} = useGroupStore();

View File

@@ -361,6 +361,7 @@
import GroupCalendarEventCard from '../../../views/Tools/components/GroupCalendarEventCard.vue';
import InstanceActionBar from '../../InstanceActionBar.vue';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const props = defineProps({
showGroupPostEditDialog: {
@@ -375,7 +376,7 @@
const { t } = useI18n();
const { showUserDialog } = useUserStore();
const { groupDialog } = storeToRefs(useGroupStore());
const { lastLocation } = storeToRefs(useLocationStore());
const { showFullscreenImageDialog } = useGalleryStore();

View File

@@ -212,15 +212,16 @@
import { downloadAndSaveJson, hasGroupPermission, userImage } from '../../../shared/utils';
import { useGroupStore, useUserStore } from '../../../stores';
import { applyGroupMember, handleGroupMember } from '../../../coordinators/groupCoordinator';
import { groupDialogSortingOptions } from '../../../shared/constants';
import { useGroupMembers } from './useGroupMembers';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const { t } = useI18n();
const { showUserDialog } = useUserStore();
const { currentUser } = storeToRefs(useUserStore());
const { groupDialog } = storeToRefs(useGroupStore());
const { applyGroupMember, handleGroupMember } = useGroupStore();
const {
isGroupMembersDone,

View File

@@ -122,6 +122,7 @@
import { useI18n } from 'vue-i18n';
import { useAppearanceSettingsStore, useGalleryStore, useGroupStore, useUserStore } from '../../../stores';
import { applyGroupMember, handleGroupMember, handleGroupMemberProps } from '../../../coordinators/groupCoordinator';
import { hasGroupPermission, userImage, userImageFull } from '../../../shared/utils';
import { groupDialogFilterOptions, groupDialogSortingOptions } from '../../../shared/constants';
import { groupRequest, userRequest } from '../../../api';
@@ -138,14 +139,14 @@
import GroupModerationInvitesTab from './GroupModerationInvitesTab.vue';
import GroupModerationLogsTab from './GroupModerationLogsTab.vue';
import GroupModerationMembersTab from './GroupModerationMembersTab.vue';
import { showUserDialog } from '../../../coordinators/userCoordinator';
// ── Stores ───────────────────────────────────────────────────
const appearanceSettingsStore = useAppearanceSettingsStore();
const { randomUserColours } = storeToRefs(appearanceSettingsStore);
const { showUserDialog } = useUserStore();
const { currentUser } = storeToRefs(useUserStore());
const { groupDialog, groupMemberModeration } = storeToRefs(useGroupStore());
const { applyGroupMember, handleGroupMember, handleGroupMemberProps } = useGroupStore();
const { showFullscreenImageDialog } = useGalleryStore();
const { t } = useI18n();

View File

@@ -43,8 +43,9 @@
import { createColumns } from './previousInstancesInfoColumns.jsx';
import { database } from '../../../service/database';
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
import { lookupUser } from '../../../coordinators/userCoordinator';
const { lookupUser } = useUserStore();
const { previousInstancesInfoDialog, previousInstancesInfoState } = storeToRefs(useInstanceStore());
const { gameLogIsFriend, gameLogIsFavorite } = useGameLogStore();
const { t } = useI18n();

View File

@@ -91,6 +91,7 @@
useNotificationStore,
useUserStore
} from '../../../stores';
import { showGroupDialog } from '../../../coordinators/groupCoordinator';
import { copyToClipboard } from '../../../shared/utils';
import { formatJsonVars } from '../../../shared/utils/base/ui';
import { miscRequest } from '../../../api';
@@ -139,11 +140,12 @@
const instanceStore = useInstanceStore();
const { userDialog, languageDialog, currentUser, isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
const { cachedUsers, showUserDialog, refreshUserDialogAvatars, showSendBoopDialog } = useUserStore();
const { cachedUsers, showSendBoopDialog } = useUserStore();
const { showFavoriteDialog } = useFavoriteStore();
const { showAvatarDialog, showAvatarAuthorDialog } = useAvatarStore();
import { showAvatarDialog, showAvatarAuthorDialog } from '../../../coordinators/avatarCoordinator';
import { showUserDialog, refreshUserDialogAvatars } from '../../../coordinators/userCoordinator';
const { showGroupDialog, showModerateGroupDialog } = useGroupStore();
const { showModerateGroupDialog } = useGroupStore();
const { inviteGroupDialog } = storeToRefs(useGroupStore());
const { lastLocation, lastLocationDestination } = storeToRefs(useLocationStore());
const { refreshInviteMessageTableData } = useInviteStore();

View File

@@ -119,7 +119,7 @@
const { userDialog, currentUser } = storeToRefs(userStore);
const { sortUserDialogAvatars, refreshUserDialogAvatars } = userStore;
const { showAvatarDialog, lookupAvatars } = useAvatarStore();
import { showAvatarDialog, lookupAvatars } from '../../../coordinators/avatarCoordinator';
const { cachedAvatars } = useAvatarStore();
const { avatarRemoteDatabase } = storeToRefs(useAdvancedSettingsStore());

View File

@@ -76,6 +76,7 @@
import DeprecationAlert from '@/components/DeprecationAlert.vue';
import { useFavoriteStore, useUserStore, useWorldStore } from '../../../stores';
import { showWorldDialog } from '../../../coordinators/worldCoordinator';
import { handleFavoriteWorldList } from '../../../coordinators/favoriteCoordinator';
import { favoriteRequest } from '../../../api';
@@ -83,7 +84,7 @@
const { userDialog, currentUser } = storeToRefs(useUserStore());
const { favoriteLimits } = storeToRefs(useFavoriteStore());
const { showWorldDialog } = useWorldStore();
const favoriteWorldsTab = ref('0');
const userDialogFavoriteWorldsRequestId = ref(0);

View File

@@ -372,15 +372,7 @@
import { useI18n } from 'vue-i18n';
import { useAuthStore, useGroupStore, useUiStore, useUserStore } from '../../../stores';
import { compareByMemberCount, compareByName } from '../../../shared/utils';
import { groupRequest } from '../../../api';
import { useOptionKeySelect } from '../../../composables/useOptionKeySelect';
import { userDialogGroupSortingOptions } from '../../../shared/constants';
const { t } = useI18n();
const { userDialog, currentUser, isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
const {
import {
showGroupDialog,
applyGroup,
saveCurrentUserGroups,
@@ -389,7 +381,15 @@
leaveGroupPrompt,
setGroupVisibility,
handleGroupList
} = useGroupStore();
} from '../../../coordinators/groupCoordinator';
import { compareByMemberCount, compareByName } from '../../../shared/utils';
import { groupRequest } from '../../../api';
import { useOptionKeySelect } from '../../../composables/useOptionKeySelect';
import { userDialogGroupSortingOptions } from '../../../shared/constants';
const { t } = useI18n();
const { userDialog, currentUser, isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
const { currentUserGroups, inGameGroupOrder } = storeToRefs(useGroupStore());
const { cachedConfig } = storeToRefs(useAuthStore());
const { shiftHeld } = storeToRefs(useUiStore());

View File

@@ -501,9 +501,11 @@
useUserStore,
useWorldStore
} from '../../../stores';
import { showWorldDialog } from '../../../coordinators/worldCoordinator';
import { queryRequest, userRequest } from '../../../api';
import InstanceActionBar from '../../InstanceActionBar.vue';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const EditNoteAndMemoDialog = defineAsyncComponent(() => import('./EditNoteAndMemoDialog.vue'));
@@ -518,8 +520,8 @@
const { bioLanguage, translationApi, translationApiType } = storeToRefs(useAdvancedSettingsStore());
const { translateText } = useAdvancedSettingsStore();
const { userDialog, currentUser } = storeToRefs(useUserStore());
const { showUserDialog, toggleSharedConnectionsOptOut, toggleDiscordFriendsOptOut } = useUserStore();
const { showWorldDialog } = useWorldStore();
const { toggleSharedConnectionsOptOut, toggleDiscordFriendsOptOut } = useUserStore();
const { showGroupDialog } = useGroupStore();
const { lastLocation } = storeToRefs(useLocationStore());
const { showFullscreenImageDialog } = useGalleryStore();

View File

@@ -73,12 +73,13 @@
import { useUserStore } from '../../../stores';
import { userDialogMutualFriendSortingOptions } from '../../../shared/constants';
import { userRequest } from '../../../api';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const { t } = useI18n();
const userStore = useUserStore();
const { userDialog, currentUser } = storeToRefs(userStore);
const { cachedUsers, showUserDialog } = userStore;
const { cachedUsers } = userStore;
const { selectedKey: userDialogMutualFriendSortingKey, selectByKey: setUserDialogMutualFriendSortingByKey } =
useOptionKeySelect(

View File

@@ -86,6 +86,7 @@
import { useI18n } from 'vue-i18n';
import { useUserStore, useWorldStore } from '../../../stores';
import { showWorldDialog } from '../../../coordinators/worldCoordinator';
import { userDialogWorldOrderOptions, userDialogWorldSortingOptions } from '../../../shared/constants/';
import { queryRequest } from '../../../api';
import { useOptionKeySelect } from '../../../composables/useOptionKeySelect';
@@ -94,7 +95,7 @@
const userStore = useUserStore();
const { userDialog, currentUser } = storeToRefs(userStore);
const { cachedWorlds, showWorldDialog } = useWorldStore();
const { cachedWorlds } = useWorldStore();
const userDialogWorldsRequestId = ref(0);

View File

@@ -110,6 +110,7 @@
import { useI18n } from 'vue-i18n';
import { useWorldStore } from '../../../stores';
import { showWorldDialog } from '../../../coordinators/worldCoordinator';
import { worldRequest } from '../../../api';
const props = defineProps({
@@ -133,7 +134,7 @@
const emit = defineEmits(['update:isSetWorldTagsDialogVisible']);
const { showWorldDialog } = useWorldStore();
const { t } = useI18n();

View File

@@ -395,6 +395,7 @@
useUserStore,
useWorldStore
} from '../../../stores';
import { showWorldDialog } from '../../../coordinators/worldCoordinator';
import {
DropdownMenu,
DropdownMenuContent,
@@ -412,15 +413,16 @@
import ImageCropDialog from '../ImageCropDialog.vue';
import WorldDialogInfoTab from './WorldDialogInfoTab.vue';
import WorldDialogInstancesTab from './WorldDialogInstancesTab.vue';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const SetWorldTagsDialog = defineAsyncComponent(() => import('./SetWorldTagsDialog.vue'));
const WorldAllowedDomainsDialog = defineAsyncComponent(() => import('./WorldAllowedDomainsDialog.vue'));
const NewInstanceDialog = defineAsyncComponent(() => import('../NewInstanceDialog.vue'));
const { showUserDialog } = useUserStore();
const { currentUser, userDialog } = storeToRefs(useUserStore());
const { worldDialog } = storeToRefs(useWorldStore());
const { cachedWorlds, showWorldDialog } = useWorldStore();
const { cachedWorlds } = useWorldStore();
const { lastLocation } = storeToRefs(useLocationStore());
const { canOpenInstanceInGame } = useInviteStore();
const { showFavoriteDialog } = useFavoriteStore();

View File

@@ -118,11 +118,12 @@
} from '../../../stores';
import InstanceActionBar from '../../InstanceActionBar.vue';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const { t } = useI18n();
const { isAgeGatedInstancesVisible } = storeToRefs(useAppearanceSettingsStore());
const { showUserDialog } = useUserStore();
const { currentUser } = storeToRefs(useUserStore());
const { worldDialog } = storeToRefs(useWorldStore());
const { lastLocation } = storeToRefs(useLocationStore());

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);
}
}
});
}

View File

@@ -7,6 +7,7 @@ import {
useUpdateLoopStore,
useUserStore
} from '../stores';
import { getCurrentUser } from '../coordinators/userCoordinator';
import { AppDebug } from './appConfig.js';
import { escapeTag } from '../shared/utils';
import { i18n } from '../plugin/i18n';
@@ -207,7 +208,7 @@ export function request(endpoint, options) {
) {
// trigger 2FA dialog }
if (!authStore.twoFactorAuthDialogVisible) {
userStore.getCurrentUser();
getCurrentUser();
}
$throw(401, t('api.status_code.401'), endpoint);
}

View File

@@ -12,6 +12,13 @@ import {
useUiStore,
useUserStore
} from '../stores';
import { applyUser, applyCurrentUser } from '../coordinators/userCoordinator';
import {
onGroupLeft,
applyGroup,
getGroupDialogGroup,
handleGroupMember
} from '../coordinators/groupCoordinator';
import { escapeTag, parseLocation } from '../shared/utils';
import { AppDebug } from './appConfig';
import { groupRequest } from '../api';
@@ -260,7 +267,7 @@ function handlePipeline(args) {
break;
case 'friend-add':
userStore.applyUser(content.user);
applyUser(content.user);
friendStore.handleFriendAdd({
params: {
userId: content.userId
@@ -298,7 +305,7 @@ function handlePipeline(args) {
...content.user
};
userStore.applyUser(onlineJson);
applyUser(onlineJson);
} else {
console.error('friend-online missing user id', content);
runUpdateFriendFlow(content.userId, 'online');
@@ -321,7 +328,7 @@ function handlePipeline(args) {
...content.user
};
userStore.applyUser(activeJson);
applyUser(activeJson);
} else {
console.error('friend-active missing user id', content);
runUpdateFriendFlow(content.userId, 'active');
@@ -342,11 +349,11 @@ function handlePipeline(args) {
travelingToWorld: 'offline',
travelingToInstance: 'offline'
};
userStore.applyUser(offlineJson);
applyUser(offlineJson);
break;
case 'friend-update':
userStore.applyUser(content.user);
applyUser(content.user);
break;
case 'friend-location':
@@ -365,7 +372,7 @@ function handlePipeline(args) {
travelingToWorld: $travelingToLocation1.worldId,
travelingToInstance: $travelingToLocation1.instanceId
};
userStore.applyUser(jankLocationJson);
applyUser(jankLocationJson);
break;
}
const locationJson = {
@@ -378,12 +385,12 @@ function handlePipeline(args) {
...content.user,
state: 'online' // JANK
};
userStore.applyUser(locationJson);
applyUser(locationJson);
break;
case 'user-update':
userStore.applyCurrentUser(content.user);
applyCurrentUser(content.user);
break;
case 'user-location':
@@ -410,14 +417,14 @@ function handlePipeline(args) {
break;
case 'group-left':
groupStore.onGroupLeft(content.groupId);
onGroupLeft(content.groupId);
break;
case 'group-role-updated':
const groupId = content.role.groupId;
groupRequest
.getGroup({ groupId, includeRoles: true })
.then((args) => groupStore.applyGroup(args.json));
.then((args) => applyGroup(args.json));
console.log('group-role-updated', content);
// content {
@@ -446,9 +453,9 @@ function handlePipeline(args) {
groupStore.groupDialog.visible &&
groupStore.groupDialog.id === groupId1
) {
groupStore.getGroupDialogGroup(groupId1);
getGroupDialogGroup(groupId1);
}
groupStore.handleGroupMember({
handleGroupMember({
json: member,
params: {
groupId: groupId1

View File

@@ -16,6 +16,7 @@ import { escapeTag } from '../shared/utils';
import { initWebsocket } from '../service/websocket';
import { request } from '../service/request';
import { runHandleAutoLoginFlow } from '../coordinators/authAutoLoginCoordinator';
import { getCurrentUser } from '../coordinators/userCoordinator';
import { useAdvancedSettingsStore } from './settings/advanced';
import { useGeneralSettingsStore } from './settings/general';
import { useModalStore } from './modal';
@@ -197,8 +198,7 @@ export const useAuthStore = defineStore('Auth', () => {
throw err;
})
.then(() => {
userStore
.getCurrentUser()
getCurrentUser()
.finally(() => {
loginForm.value.loading = false;
})
@@ -671,7 +671,7 @@ export const useAuthStore = defineStore('Auth', () => {
clearCookiesTryLogin();
})
.then(() => {
userStore.getCurrentUser();
getCurrentUser();
});
})
.catch(() => {
@@ -713,7 +713,7 @@ export const useAuthStore = defineStore('Auth', () => {
clearCookiesTryLogin();
})
.then(() => {
userStore.getCurrentUser();
getCurrentUser();
});
})
.catch(() => {
@@ -756,7 +756,7 @@ export const useAuthStore = defineStore('Auth', () => {
promptEmailOTP();
})
.then(() => {
userStore.getCurrentUser();
getCurrentUser();
});
})
.catch(() => {

View File

@@ -1,46 +1,13 @@
import { nextTick, ref, watch } from 'vue';
import { ref, watch } from 'vue';
import { defineStore } from 'pinia';
import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import {
checkVRChatCache,
createDefaultAvatarRef,
extractFileId,
getAvailablePlatforms,
getBundleDateSize,
getPlatformInfo,
replaceBioSymbols,
sanitizeEntityJson,
storeAvatarImage
checkVRChatCache
} 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 '../coordinators/favoriteCoordinator';
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();
@@ -83,145 +50,17 @@ export const useAvatarStore = defineStore('Avatar', () => {
cachedAvatarModerations.clear();
avatarHistory.value = [];
if (isLoggedIn) {
getAvatarHistory();
preloadOwnAvatars();
import('../coordinators/avatarCoordinator').then(
({ getAvatarHistory, preloadOwnAvatars }) => {
getAvatarHistory();
preloadOwnAvatars();
}
);
}
},
{ flush: 'sync' }
);
/**
* @param {object} json
* @returns {object} ref
*/
function applyAvatar(json) {
sanitizeEntityJson(json, ['name', 'description']);
let ref = cachedAvatars.get(json.id);
if (typeof ref === 'undefined') {
ref = createDefaultAvatarRef(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);
}
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
*/
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 })
: 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
);
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 {boolean} value
*/
@@ -249,6 +88,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
* @returns {Promise<string[]>}
*/
async function getAvatarGallery(avatarId) {
const { queryRequest } = await import('../api');
const D = avatarDialog.value;
const args = await queryRequest
.fetch('avatarGallery', { avatarId })
@@ -341,54 +181,6 @@ export const useAvatarStore = defineStore('Avatar', () => {
}
}
/**
*
* @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);
});
}
/**
*
*/
@@ -397,423 +189,21 @@ export const useAvatarStore = defineStore('Avatar', () => {
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,
loadingToastId,
cachedAvatarModerations,
cachedAvatars,
cachedAvatarNames,
showAvatarDialog,
applyAvatarModeration,
resetCachedAvatarModerations,
getAvatarGallery,
updateVRChatAvatarCache,
getAvatarHistory,
addAvatarToHistory,
applyAvatar,
promptClearAvatarHistory,
getAvatarName,
lookupAvatars,
selectAvatarWithConfirmation,
selectAvatarWithoutConfirmation,
clearAvatarHistory,
setAvatarDialogVisible,
setAvatarDialogIsFavorite,
setAvatarDialogLoading,
showAvatarAuthorDialog,
addAvatarWearTime,
preloadOwnAvatars
setAvatarDialogLoading
};
});

View File

@@ -25,6 +25,7 @@ import {
runPendingOfflineTickFlow,
runUpdateFriendFlow
} from '../coordinators/friendPresenceCoordinator';
import { applyUser } from '../coordinators/userCoordinator';
import { AppDebug } from '../service/appConfig';
import { database } from '../service/database';
import { useAppearanceSettingsStore } from './settings/appearance';
@@ -981,7 +982,7 @@ export const useFriendStore = defineStore('Friend', () => {
const sqlValues = [];
const friends = await refreshFriends();
for (const friend of friends) {
const ref = userStore.applyUser(friend);
const ref = applyUser(friend);
const row = {
userId: ref.id,
displayName: ref.displayName,

View File

@@ -13,6 +13,10 @@ import { useAvatarStore } from './avatar';
import { useFavoriteStore } from './favorite';
import { useFriendStore } from './friend';
import { useGroupStore } from './group';
import { showGroupDialog } from '../coordinators/groupCoordinator';
import { showWorldDialog } from '../coordinators/worldCoordinator';
import { showAvatarDialog } from '../coordinators/avatarCoordinator';
import { showUserDialog } from '../coordinators/userCoordinator';
import { useUserStore } from './user';
import { useWorldStore } from './world';
@@ -166,16 +170,16 @@ export const useGlobalSearchStore = defineStore('GlobalSearch', () => {
switch (item.type) {
case 'friend':
userStore.showUserDialog(item.id);
showUserDialog(item.id);
break;
case 'avatar':
avatarStore.showAvatarDialog(item.id);
showAvatarDialog(item.id);
break;
case 'world':
worldStore.showWorldDialog(item.id);
showWorldDialog(item.id);
break;
case 'group':
groupStore.showGroupDialog(item.id);
showGroupDialog(item.id);
break;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -44,6 +44,8 @@ import { useFriendStore } from '../friend';
import { useGameStore } from '../game';
import { useGeneralSettingsStore } from '../settings/general';
import { useGroupStore } from '../group';
import { showGroupDialog } from '../../coordinators/groupCoordinator';
import { showUserDialog } from '../../coordinators/userCoordinator';
import { useInstanceStore } from '../instance';
import { useLocationStore } from '../location';
import { useModalStore } from '../modal';
@@ -1415,10 +1417,10 @@ export const useNotificationStore = defineStore('Notification', () => {
}
switch (data[0]) {
case 'group':
groupStore.showGroupDialog(data[1]);
showGroupDialog(data[1]);
break;
case 'user':
userStore.showUserDialog(data[1]);
showUserDialog(data[1]);
break;
case 'event':
const ids = data[1].split(',');
@@ -1427,7 +1429,7 @@ export const useNotificationStore = defineStore('Notification', () => {
return;
}
groupStore.showGroupDialog(ids[0]);
showGroupDialog(ids[0]);
// ids[1] cal_ is the event id
break;
case 'openNotificationLink':

View File

@@ -20,6 +20,8 @@ import { photonEmojis, photonEventType } from '../shared/constants/photon';
import { AppDebug } from '../service/appConfig';
import { database } from '../service/database';
import { useAvatarStore } from './avatar';
import { applyAvatar } from '../coordinators/avatarCoordinator';
import { showUserDialog, lookupUser, applyUser } from '../coordinators/userCoordinator';
import { useFavoriteStore } from './favorite';
import { useFriendStore } from './friend';
import { useGameLogStore } from './gameLog';
@@ -424,9 +426,9 @@ export const usePhotonStore = defineStore('Photon', () => {
const ref = photonLobby.value.get(photonId);
if (typeof ref !== 'undefined') {
if (typeof ref.id !== 'undefined') {
userStore.showUserDialog(ref.id);
showUserDialog(ref.id);
} else if (typeof ref.displayName !== 'undefined') {
userStore.lookupUser(ref);
lookupUser(ref);
}
} else {
toast.error('No user info available');
@@ -1475,7 +1477,7 @@ export const usePhotonStore = defineStore('Photon', () => {
typeof ref.id !== 'undefined' &&
ref.currentAvatarImageUrl !== user.currentAvatarImageUrl
) {
userStore.applyUser({
applyUser({
...ref,
currentAvatarImageUrl: user.currentAvatarImageUrl,
currentAvatarThumbnailImageUrl:
@@ -1803,7 +1805,7 @@ export const usePhotonStore = defineStore('Photon', () => {
}
}
}
avatarStore.applyAvatar({
applyAvatar({
id: avatar.id,
authorId: avatar.authorId,
authorName: avatar.authorName,

View File

@@ -12,6 +12,10 @@ import { useAppearanceSettingsStore } from './settings/appearance';
import { useAvatarStore } from './avatar';
import { useFriendStore } from './friend';
import { useGroupStore } from './group';
import { showGroupDialog } from '../coordinators/groupCoordinator';
import { showWorldDialog } from '../coordinators/worldCoordinator';
import { showAvatarDialog } from '../coordinators/avatarCoordinator';
import { applyUser, showUserDialog, lookupUser } from '../coordinators/userCoordinator';
import { useModalStore } from './modal';
import { useUserStore } from './user';
import { useWorldStore } from './world';
@@ -93,7 +97,7 @@ export const useSearchStore = defineStore('Search', () => {
console.error('getUsers gave us garbage', json);
continue;
}
userStore.applyUser(json);
applyUser(json);
}
const map = new Map();
@@ -210,10 +214,10 @@ export const useSearchStore = defineStore('Search', () => {
} else {
router.push({ name: 'search' });
searchText.value = searchTerm;
userStore.lookupUser({ displayName: searchTerm });
lookupUser({ displayName: searchTerm });
}
} else {
userStore.showUserDialog(value);
showUserDialog(value);
}
}
@@ -273,15 +277,15 @@ export const useSearchStore = defineStore('Search', () => {
const type = urlPathSplit[2];
if (type === 'user') {
const userId = urlPathSplit[3];
userStore.showUserDialog(userId);
showUserDialog(userId);
return true;
} else if (type === 'avatar') {
const avatarId = urlPathSplit[3];
avatarStore.showAvatarDialog(avatarId);
showAvatarDialog(avatarId);
return true;
} else if (type === 'group') {
const groupId = urlPathSplit[3];
groupStore.showGroupDialog(groupId);
showGroupDialog(groupId);
return true;
}
} else if (input.startsWith('https://vrc.group/')) {
@@ -295,16 +299,16 @@ export const useSearchStore = defineStore('Search', () => {
input.substring(0, 4) === 'usr_' ||
/^[A-Za-z0-9]{10}$/g.test(input)
) {
userStore.showUserDialog(input);
showUserDialog(input);
return true;
} else if (
input.substring(0, 5) === 'avtr_' ||
input.substring(0, 2) === 'b_'
) {
avatarStore.showAvatarDialog(input);
showAvatarDialog(input);
return true;
} else if (input.substring(0, 4) === 'grp_') {
groupStore.showGroupDialog(input);
showGroupDialog(input);
return true;
}
return false;
@@ -331,7 +335,7 @@ export const useSearchStore = defineStore('Search', () => {
const urlPathSplit = urlPath.split('/');
if (urlPathSplit.length >= 4 && urlPathSplit[2] === 'world') {
worldId = urlPathSplit[3];
worldStore.showWorldDialog(worldId);
showWorldDialog(worldId);
return true;
} else if (urlPath.substring(5, 12) === '/launch') {
const urlParams = new URLSearchParams(url.search);
@@ -343,10 +347,10 @@ export const useSearchStore = defineStore('Search', () => {
if (shortName) {
return verifyShortName(location, shortName);
}
worldStore.showWorldDialog(location);
showWorldDialog(location);
return true;
} else if (worldId) {
worldStore.showWorldDialog(worldId);
showWorldDialog(worldId);
return true;
}
}
@@ -360,7 +364,7 @@ export const useSearchStore = defineStore('Search', () => {
input = `https://vrchat.com/home/launch?worldId=${input}`;
return directAccessWorld(input);
}
worldStore.showWorldDialog(input.trim());
showWorldDialog(input.trim());
return true;
}
return false;
@@ -399,7 +403,7 @@ export const useSearchStore = defineStore('Search', () => {
groupRequest.groupStrictsearch({ query: shortCode }).then((args) => {
for (const group of args.json) {
if (`${group.shortCode}.${group.discriminator}` === shortCode) {
groupStore.showGroupDialog(group.id);
showGroupDialog(group.id);
break;
}
}
@@ -414,11 +418,11 @@ export const useSearchStore = defineStore('Search', () => {
const newLocation = args.json.location;
const newShortName = args.json.shortName;
if (newShortName) {
worldStore.showWorldDialog(newLocation, newShortName);
showWorldDialog(newLocation, newShortName);
} else if (newLocation) {
worldStore.showWorldDialog(newLocation);
showWorldDialog(newLocation);
} else {
worldStore.showWorldDialog(location);
showWorldDialog(location);
}
return args;
});

View File

@@ -10,6 +10,10 @@ import { updateLocalizedStrings } from '../plugin/i18n';
import { useAppearanceSettingsStore } from './settings/appearance';
import { useAvatarStore } from './avatar';
import { useGroupStore } from './group';
import { showGroupDialog } from '../coordinators/groupCoordinator';
import { showWorldDialog } from '../coordinators/worldCoordinator';
import { showAvatarDialog } from '../coordinators/avatarCoordinator';
import { showUserDialog } from '../coordinators/userCoordinator';
import { useInstanceStore } from './instance';
import { useNotificationStore } from './notification';
import { useSearchStore } from './search';
@@ -142,19 +146,19 @@ export const useUiStore = defineStore('Ui', () => {
}
jumpDialogCrumb(index);
if (item.type === 'user') {
userStore.showUserDialog(item.id);
showUserDialog(item.id);
return;
}
if (item.type === 'world') {
worldStore.showWorldDialog(item.tag, item.shortName);
showWorldDialog(item.tag, item.shortName);
return;
}
if (item.type === 'avatar') {
avatarStore.showAvatarDialog(item.id);
showAvatarDialog(item.id);
return;
}
if (item.type === 'group') {
groupStore.showGroupDialog(item.id);
showGroupDialog(item.id);
return;
}
if (item.type === 'previous-instances-user') {

View File

@@ -12,6 +12,8 @@ import { useFriendStore } from './friend';
import { useGameLogStore } from './gameLog';
import { useGameStore } from './game';
import { useGroupStore } from './group';
import { handleGroupUserInstances } from '../coordinators/groupCoordinator';
import { getCurrentUser, updateAutoStateChange } from '../coordinators/userCoordinator';
import { useModerationStore } from './moderation';
import { useUserStore } from './user';
import { useVRCXUpdaterStore } from './vrcxUpdater';
@@ -73,7 +75,7 @@ export const useUpdateLoopStore = defineStore('UpdateLoop', () => {
if (watchState.isLoggedIn) {
if (--state.nextCurrentUserRefresh <= 0) {
state.nextCurrentUserRefresh = 300; // 5min
userStore.getCurrentUser();
getCurrentUser();
}
if (--state.nextFriendsRefresh <= 0) {
state.nextFriendsRefresh = 3600; // 1hour
@@ -92,7 +94,7 @@ export const useUpdateLoopStore = defineStore('UpdateLoop', () => {
state.nextGroupInstanceRefresh = 300; // 5min
const args =
await groupRequest.getUsersGroupInstances();
groupStore.handleGroupUserInstances(args);
handleGroupUserInstances(args);
}
AppApi.CheckGameRunning();
}
@@ -122,7 +124,7 @@ export const useUpdateLoopStore = defineStore('UpdateLoop', () => {
}
if (--state.nextAutoStateChange <= 0) {
state.nextAutoStateChange = 3;
userStore.updateAutoStateChange();
updateAutoStateChange();
}
if (LINUX && --state.nextGetLogCheck <= 0) {
state.nextGetLogCheck = 0.5;

File diff suppressed because it is too large Load Diff

View File

@@ -32,6 +32,10 @@ import { useGalleryStore } from './gallery';
import { useGameLogStore } from './gameLog';
import { useGameStore } from './game';
import { useGroupStore } from './group';
import { showGroupDialog } from '../coordinators/groupCoordinator';
import { showWorldDialog } from '../coordinators/worldCoordinator';
import { showAvatarDialog, selectAvatarWithConfirmation, selectAvatarWithoutConfirmation } from '../coordinators/avatarCoordinator';
import { showUserDialog, addCustomTag } from '../coordinators/userCoordinator';
import { useInstanceStore } from './instance';
import { useLocationStore } from './location';
import { useModalStore } from './modal';
@@ -335,7 +339,7 @@ export const useVrcxStore = defineStore('Vrcx', () => {
let entry;
switch (data.MsgType) {
case 'CustomTag':
userStore.addCustomTag(data);
addCustomTag(data);
break;
case 'ClearCustomTags':
userStore.customUserTags.forEach((value, key) => {
@@ -669,17 +673,17 @@ export const useVrcxStore = defineStore('Vrcx', () => {
!searchStore.directAccessWorld(input.replace('world/', ''))
) {
// fallback for mangled world ids
worldStore.showWorldDialog(commandArg);
showWorldDialog(commandArg);
}
break;
case 'avatar':
avatarStore.showAvatarDialog(commandArg);
showAvatarDialog(commandArg);
break;
case 'user':
userStore.showUserDialog(commandArg);
showUserDialog(commandArg);
break;
case 'group':
groupStore.showGroupDialog(commandArg);
showGroupDialog(commandArg);
break;
case 'local-favorite-world':
console.log('local-favorite-world', commandArg);
@@ -701,7 +705,7 @@ export const useVrcxStore = defineStore('Vrcx', () => {
break;
}
avatarRequest.getAvatar({ avatarId: avatarIdFav }).then(() => {
avatarStore.showAvatarDialog(avatarIdFav);
showAvatarDialog(avatarIdFav);
addLocalAvatarFavorite(
avatarIdFav,
avatarGroup
@@ -722,12 +726,11 @@ export const useVrcxStore = defineStore('Vrcx', () => {
break;
}
if (advancedSettingsStore.showConfirmationOnSwitchAvatar) {
avatarStore.selectAvatarWithConfirmation(avatarId);
selectAvatarWithConfirmation(avatarId);
// Makes sure the window is focused
shouldFocusWindow = true;
} else {
avatarStore
.selectAvatarWithoutConfirmation(avatarId)
selectAvatarWithoutConfirmation(avatarId)
.then(() => {
toast.success('Avatar changed via launch command');
});

View File

@@ -1,39 +1,12 @@
import { nextTick, reactive, shallowReactive, watch } from 'vue';
import { reactive, shallowReactive, watch } from 'vue';
import { defineStore } from 'pinia';
import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import {
checkVRChatCache,
createDefaultWorldRef,
evictMapCache,
getAvailablePlatforms,
getBundleDateSize,
getWorldMemo,
isRealInstance,
parseLocation,
sanitizeEntityJson
checkVRChatCache
} 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 '../coordinators/favoriteCoordinator';
import { useFavoriteStore } from './favorite';
import { useInstanceStore } from './instance';
import { useLocationStore } from './location';
import { useUiStore } from './ui';
import { useUserStore } from './user';
import { watchState } from '../service/watchState';
export const useWorldStore = defineStore('World', () => {
const locationStore = useLocationStore();
const favoriteStore = useFavoriteStore();
const instanceStore = useInstanceStore();
const userStore = useUserStore();
const uiStore = useUiStore();
const { t } = useI18n();
const worldDialog = reactive({
visible: false,
loading: false,
@@ -69,145 +42,14 @@ export const useWorldStore = defineStore('World', () => {
worldDialog.visible = false;
cachedWorlds.clear();
if (isLoggedIn) {
preloadOwnWorlds();
import('../coordinators/worldCoordinator').then(
({ preloadOwnWorlds }) => preloadOwnWorlds()
);
}
},
{ flush: 'sync' }
);
/**
*
* @param {string} tag
* @param {string} shortName
* @param options
*/
function showWorldDialog(tag, shortName = null, options = {}) {
const D = 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;
updateVRChatWorldCache();
queryRequest
.fetch('worldPersistData', {
worldId: D.id
})
.then((args) => {
if (
args.params.worldId === worldDialog.id &&
worldDialog.visible
) {
worldDialog.hasPersistData =
args.json !== false;
}
});
}
});
}
/**
* @param {boolean} value
*/
@@ -250,99 +92,12 @@ export const useWorldStore = defineStore('World', () => {
}
}
/**
*
* @param WorldCache
*/
function cleanupWorldCache(WorldCache) {
evictMapCache(WorldCache, 10000, () => false, {
logLabel: 'World cache cleanup'
});
}
/**
*
* @param {object} json
* @returns {object} ref
*/
function applyWorld(json) {
sanitizeEntityJson(json, ['name', 'description']);
let ref = cachedWorlds.get(json.id);
if (typeof ref === 'undefined') {
ref = createDefaultWorldRef(json);
cleanupWorldCache(cachedWorlds);
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();
}
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.
*/
async function preloadOwnWorlds() {
const params = {
n: 50,
offset: 0,
sort: 'updated',
order: 'descending',
releaseStatus: 'all',
user: 'me'
};
await processBulk({
fn: (p) => worldRequest.getWorlds(p),
N: -1,
params,
handle: (args) => {
for (const json of args.json) {
applyWorld(json);
}
}
});
}
return {
worldDialog,
cachedWorlds,
showWorldDialog,
setWorldDialogVisible,
setWorldDialogIsFavorite,
setWorldDialogLoading,
updateVRChatWorldCache,
applyWorld,
preloadOwnWorlds
updateVRChatWorldCache
};
});

View File

@@ -28,9 +28,10 @@
import { timeToText } from '../../../shared/utils';
import * as echarts from 'echarts';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const { isDarkMode, dtHour12 } = storeToRefs(useAppearanceSettingsStore());
const { showUserDialog } = useUserStore();
const { currentUser } = storeToRefs(useUserStore());
const props = defineProps({

View File

@@ -275,6 +275,7 @@
useUserStore
} from '../../../stores';
import { userImage, userStatusClass } from '../../../shared/utils';
import { showUserDialog } from '../../../coordinators/userCoordinator';
import { database } from '../../../service/database';
import { watchState } from '../../../service/watchState';
@@ -290,7 +291,7 @@
const { currentUser } = storeToRefs(userStore);
const { isDarkMode } = storeToRefs(appearanceStore);
const cachedUsers = userStore.cachedUsers;
const showUserDialog = (userId) => userStore.showUserDialog(userId);
const fetchState = chartsStore.mutualGraphFetchState;
const status = chartsStore.mutualGraphStatus;

View File

@@ -519,7 +519,7 @@
handleFavoriteGroup
} = favoriteStore;
const { avatarHistory } = storeToRefs(useAvatarStore());
const { promptClearAvatarHistory, showAvatarDialog, applyAvatar } = useAvatarStore();
import { promptClearAvatarHistory, showAvatarDialog, applyAvatar } from '../../coordinators/avatarCoordinator';
const { isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
const { t } = useI18n();

View File

@@ -388,6 +388,7 @@
DropdownMenuTrigger
} from '../../components/ui/dropdown-menu';
import { useAppearanceSettingsStore, useFavoriteStore, useModalStore, useWorldStore } from '../../stores';
import { showWorldDialog } from '../../coordinators/worldCoordinator';
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../../components/ui/resizable';
import { favoriteRequest, worldRequest } from '../../api';
import { debounce } from '../../shared/utils';
@@ -436,7 +437,7 @@
handleFavoriteGroup,
localWorldFavoritesList
} = favoriteStore;
const { showWorldDialog } = useWorldStore();
const {
cardScale: worldCardScale,

View File

@@ -170,7 +170,7 @@
const { favoriteAvatarGroups } = storeToRefs(useFavoriteStore());
const { showFavoriteDialog } = useFavoriteStore();
const { selectAvatarWithConfirmation } = useAvatarStore();
import { selectAvatarWithConfirmation } from '../../../coordinators/avatarCoordinator';
const { shiftHeld } = storeToRefs(useUiStore());
const { currentUser } = storeToRefs(useUserStore());

View File

@@ -75,7 +75,7 @@
const { t } = useI18n();
const { showFavoriteDialog, getCachedFavoritesByObjectId } = useFavoriteStore();
const { selectAvatarWithConfirmation } = useAvatarStore();
import { selectAvatarWithConfirmation } from '../../../coordinators/avatarCoordinator';
const { currentUser } = storeToRefs(useUserStore());
const props = defineProps({

View File

@@ -131,11 +131,12 @@
const emit = defineEmits(['update:avatarImportDialogInput']);
const { t } = useI18n();
const { showUserDialog } = useUserStore();
const { favoriteAvatarGroups, avatarImportDialogInput, avatarImportDialogVisible, localAvatarFavoriteGroups } =
storeToRefs(useFavoriteStore());
const { localAvatarFavGroupLength, getCachedFavoritesByObjectId } = useFavoriteStore();
const { showAvatarDialog, applyAvatar } = useAvatarStore();
import { showAvatarDialog, applyAvatar } from '../../../coordinators/avatarCoordinator';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const { showFullscreenImageDialog } = useGalleryStore();
const avatarImportDialog = ref({

View File

@@ -131,12 +131,13 @@
import { favoriteRequest, userRequest } from '../../../api';
import { createColumns } from './friendImportColumns.jsx';
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const { t } = useI18n();
const emit = defineEmits(['update:friendImportDialogInput']);
const { showUserDialog } = useUserStore();
const { favoriteFriendGroups, friendImportDialogInput, friendImportDialogVisible, localFriendFavoriteGroups } =
storeToRefs(useFavoriteStore());
const { showFullscreenImageDialog } = useGalleryStore();

View File

@@ -127,17 +127,19 @@
import { useI18n } from 'vue-i18n';
import { useFavoriteStore, useGalleryStore, useUserStore, useWorldStore } from '../../../stores';
import { showWorldDialog } from '../../../coordinators/worldCoordinator';
import { addLocalWorldFavorite } from '../../../coordinators/favoriteCoordinator';
import { favoriteRequest, worldRequest } from '../../../api';
import { createColumns } from './worldImportColumns.jsx';
import { removeFromArray } from '../../../shared/utils';
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const { showUserDialog } = useUserStore();
const { favoriteWorldGroups, worldImportDialogInput, worldImportDialogVisible, localWorldFavoriteGroups } =
storeToRefs(useFavoriteStore());
const { localWorldFavGroupLength, getCachedFavoritesByObjectId } = useFavoriteStore();
const { showWorldDialog } = useWorldStore();
const { showFullscreenImageDialog } = useGalleryStore();
const emit = defineEmits(['update:worldImportDialogInput']);

View File

@@ -16,7 +16,8 @@ import {
} from 'lucide-vue-next';
import { formatDateFilter, statusClass, timeToText } from '../../shared/utils';
import { i18n } from '../../plugin';
import { useUserStore, useGalleryStore } from '../../stores';
import { useGalleryStore } from '../../stores';
import { showUserDialog } from '../../coordinators/userCoordinator';
const { t } = i18n.global;
@@ -276,7 +277,7 @@ export const columns = [
header: () => t('table.feed.user'),
meta: { label: () => t('table.feed.user') },
cell: ({ row }) => {
const { showUserDialog } = useUserStore();
const original = row.original;
return (
<span

View File

@@ -148,6 +148,7 @@
import { router } from '../../plugin/router';
import { useDataTableScrollHeight } from '../../composables/useDataTableScrollHeight';
import { useVrcxVueTable } from '../../lib/table/useVrcxVueTable';
import { showUserDialog } from '../../coordinators/userCoordinator';
const { t } = useI18n();
@@ -158,7 +159,7 @@
const { getAllUserStats, getAllUserMutualCount, confirmDeleteFriend, handleFriendDelete } = useFriendStore();
const appearanceSettingsStore = useAppearanceSettingsStore();
const { randomUserColours } = storeToRefs(appearanceSettingsStore);
const { showUserDialog } = useUserStore();
const { stringComparer, friendsListSearch } = storeToRefs(useSearchStore());
const friendsListSearchFilters = ref([]);

View File

@@ -10,12 +10,13 @@ import { storeToRefs } from 'pinia';
import { formatDateFilter } from '../../shared/utils';
import { i18n } from '../../plugin';
import { useUiStore, useUserStore } from '../../stores';
import { useUiStore } from '../../stores';
import { showUserDialog } from '../../coordinators/userCoordinator';
const { t } = i18n.global;
export const createColumns = ({ onDelete, onDeletePrompt }) => {
const { showUserDialog } = useUserStore();
const { shiftHeld } = storeToRefs(useUiStore());
return [

View File

@@ -81,9 +81,10 @@
import { checkCanInvite, checkCanInviteSelf } from '../../../shared/utils/invite.js';
import Location from '../../../components/Location.vue';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const { t } = useI18n();
const { showUserDialog, showSendBoopDialog } = useUserStore();
const { showSendBoopDialog } = useUserStore();
const launchStore = useLaunchStore();
const { lastLocation, lastLocationDestination } = storeToRefs(useLocationStore());
const { isGameRunning } = storeToRefs(useGameStore());

View File

@@ -9,10 +9,10 @@ import { formatDateFilter, openExternalLink } from '../../shared/utils';
import { i18n } from '../../plugin';
import {
useInstanceStore,
useUiStore,
useUserStore,
useWorldStore
useUiStore
} from '../../stores';
import { lookupUser } from '../../coordinators/userCoordinator';
import { showWorldDialog } from '../../coordinators/worldCoordinator';
const { t } = i18n.global;
@@ -24,8 +24,9 @@ const UNACTIONABLE_TYPES = new Set([
]);
export const createColumns = ({ getCreatedAt, onDelete, onDeletePrompt }) => {
const { showWorldDialog } = useWorldStore();
const { lookupUser } = useUserStore();
const { showPreviousInstancesInfoDialog } = useInstanceStore();
const { shiftHeld } = storeToRefs(useUiStore());

View File

@@ -11,11 +11,12 @@ import { storeToRefs } from 'pinia';
import { formatDateFilter } from '../../shared/utils';
import { i18n } from '../../plugin';
import { useUiStore, useUserStore } from '../../stores';
import { showUserDialog } from '../../coordinators/userCoordinator';
const { t, te } = i18n.global;
export const createColumns = ({ onDelete, onDeletePrompt }) => {
const { showUserDialog } = useUserStore();
const { shiftHeld } = storeToRefs(useUiStore());
const { currentUser } = storeToRefs(useUserStore());

View File

@@ -347,12 +347,13 @@
import ManageTagsDialog from './ManageTagsDialog.vue';
import MyAvatarCard from './components/MyAvatarCard.vue';
import configRepository from '../../service/config.js';
import { showAvatarDialog, selectAvatarWithoutConfirmation, applyAvatar } from '../../coordinators/avatarCoordinator';
const { t } = useI18n();
const appearanceSettingsStore = useAppearanceSettingsStore();
const avatarStore = useAvatarStore();
const modalStore = useModalStore();
const { showAvatarDialog, selectAvatarWithoutConfirmation, applyAvatar } = avatarStore;
const { currentUser } = storeToRefs(useUserStore());
const pageSizes = computed(() => appearanceSettingsStore.tablePageSizes);

View File

@@ -31,13 +31,14 @@ import { checkCanInvite, formatDateFilter } from '../../shared/utils';
import { i18n } from '../../plugin';
import {
useGameStore,
useGroupStore,
useLocationStore,
useUiStore,
useUserStore,
useWorldStore,
useNotificationStore
} from '../../stores';
import { showUserDialog } from '../../coordinators/userCoordinator';
import { showWorldDialog } from '../../coordinators/worldCoordinator';
import { showGroupDialog } from '../../coordinators/groupCoordinator';
import Emoji from '../../components/Emoji.vue';
@@ -61,9 +62,8 @@ export const createColumns = ({
deleteNotificationLog,
deleteNotificationLogPrompt
}) => {
const { showUserDialog, showSendBoopDialog } = useUserStore();
const { showWorldDialog } = useWorldStore();
const { showGroupDialog } = useGroupStore();
const { showSendBoopDialog } = useUserStore();
const { shiftHeld } = storeToRefs(useUiStore());
const { currentUser } = storeToRefs(useUserStore());
const { lastLocation } = storeToRefs(useLocationStore());

View File

@@ -196,6 +196,7 @@
import ChatboxBlacklistDialog from './dialogs/ChatboxBlacklistDialog.vue';
import Timer from '../../components/Timer.vue';
import { showUserDialog, lookupUser } from '../../coordinators/userCoordinator';
const PhotonEventTable = defineAsyncComponent(() => import('./components/PhotonEventTable.vue'));
@@ -203,7 +204,7 @@
const photonStore = usePhotonStore();
const { photonLoggingEnabled, chatboxUserBlacklist } = storeToRefs(photonStore);
const { saveChatboxUserBlacklist } = photonStore;
const { showUserDialog, lookupUser } = useUserStore();
const { showWorldDialog } = useWorldStore();
const { lastLocation } = storeToRefs(useLocationStore());
const { currentInstanceLocation, currentInstanceWorld, currentInstanceUsersData } = storeToRefs(useInstanceStore());

View File

@@ -83,9 +83,11 @@
useVrcxStore,
useWorldStore
} from '../../../stores';
import { showWorldDialog } from '../../../coordinators/worldCoordinator';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../components/ui/select';
import { createColumns } from './photonEventColumns.jsx';
import { photonEventTableTypeFilterList } from '../../../shared/constants/photon';
import { lookupUser } from '../../../coordinators/userCoordinator';
const emit = defineEmits(['show-chatbox-blacklist']);
const { t } = useI18n();
@@ -106,9 +108,9 @@
const { stringComparer } = storeToRefs(useSearchStore());
const { lookupUser } = useUserStore();
const { showAvatarDialog } = useAvatarStore();
const { showWorldDialog } = useWorldStore();
const { showGroupDialog } = useGroupStore();
const { showFullscreenImageDialog } = useGalleryStore();
const { ipcEnabled } = storeToRefs(useVrcxStore());

View File

@@ -402,13 +402,14 @@
userImage
} from '../../shared/utils';
import { groupRequest, worldRequest } from '../../api';
import { showUserDialog, refreshUserDialogAvatars } from '../../coordinators/userCoordinator';
const { randomUserColours } = storeToRefs(useAppearanceSettingsStore());
const { avatarRemoteDatabase } = storeToRefs(useAdvancedSettingsStore());
const { avatarRemoteDatabaseProviderList, avatarRemoteDatabaseProvider } = storeToRefs(useAvatarProviderStore());
const { setAvatarProvider } = useAvatarProviderStore();
const { userDialog } = storeToRefs(useUserStore());
const { showUserDialog, refreshUserDialogAvatars } = useUserStore();
const { showAvatarDialog, lookupAvatars, cachedAvatars } = useAvatarStore();
const { cachedWorlds, showWorldDialog } = useWorldStore();
const { showGroupDialog } = useGroupStore();

View File

@@ -80,6 +80,7 @@
import { userImage, userStatusClass } from '../../../shared/utils';
import '@/styles/status-icon.css';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const props = defineProps({
friend: { type: Object, required: true },
@@ -89,7 +90,7 @@
const { hideNicknames } = storeToRefs(useAppearanceSettingsStore());
const { isRefreshFriendsLoading, allFavoriteFriendIds } = storeToRefs(useFriendStore());
const { confirmDeleteFriend } = useFriendStore();
const { showUserDialog } = useUserStore();
const { t } = useI18n();
const isFriendTraveling = computed(() => props.friend.ref?.location === 'traveling');

View File

@@ -216,6 +216,7 @@
import configRepository from '../../../service/config';
import '@/styles/status-icon.css';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const { t } = useI18n();
@@ -238,7 +239,7 @@
sidebarSortMethods
} = storeToRefs(appearanceSettingsStore);
const { gameLogDisabled } = storeToRefs(useAdvancedSettingsStore());
const { showUserDialog, showSendBoopDialog } = useUserStore();
const { showSendBoopDialog } = useUserStore();
const launchStore = useLaunchStore();
const { favoriteFriendGroups, groupedByGroupKeyFavoriteFriends, localFriendFavorites } =
storeToRefs(useFavoriteStore());

View File

@@ -255,6 +255,8 @@
import dayjs from 'dayjs';
import { useGameStore, useGroupStore, useLocationStore, useNotificationStore, useUserStore } from '../../../stores';
import { showGroupDialog } from '../../../coordinators/groupCoordinator';
import { showUserDialog } from '../../../coordinators/userCoordinator';
import { checkCanInvite, userImage } from '../../../shared/utils';
import Location from '../../../components/Location.vue';
@@ -461,13 +463,13 @@
if (userId.startsWith('grp_') || n.type?.startsWith('group.') || n.type === 'groupChange') {
const groupId = userId.startsWith('grp_') ? userId : n.data?.groupId || n.details?.groupId || '';
if (groupId) {
groupStore.showGroupDialog(groupId);
showGroupDialog(groupId);
return;
}
}
if (userId) {
userStore.showUserDialog(userId);
showUserDialog(userId);
return;
}

View File

@@ -66,11 +66,12 @@
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
import * as workerTimers from 'worker-timers';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const { t } = useI18n();
const { friends } = storeToRefs(useFriendStore());
const { showUserDialog } = useUserStore();
const { showFullscreenImageDialog } = useGalleryStore();
const props = defineProps({