refactor utils

This commit is contained in:
pa
2026-03-10 20:08:16 +09:00
parent ff1529920b
commit 1dfd0bf54c
40 changed files with 435 additions and 275 deletions

View File

@@ -6,10 +6,11 @@
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useGlobalSearchStore } from '../stores/globalSearch'; import { useGlobalSearchStore } from '../stores/globalSearch';
import { userImage } from '../shared/utils'; import { useUserDisplay } from '../composables/useUserDisplay';
import GlobalSearchSync from './GlobalSearchSync.vue'; import GlobalSearchSync from './GlobalSearchSync.vue';
const { userImage } = useUserDisplay();
const globalSearchStore = useGlobalSearchStore(); const globalSearchStore = useGlobalSearchStore();
const { const {
isOpen, isOpen,

View File

@@ -159,7 +159,8 @@
useModalStore, useModalStore,
useUserStore useUserStore
} from '../stores'; } from '../stores';
import { checkCanInviteSelf, formatDateFilter, hasGroupPermission, parseLocation } from '../shared/utils'; import { formatDateFilter, hasGroupPermission, parseLocation } from '../shared/utils';
import { useInviteChecks } from '../composables/useInviteChecks';
import { instanceRequest, miscRequest } from '../api'; import { instanceRequest, miscRequest } from '../api';
import { showUserDialog } from '../coordinators/userCoordinator'; import { showUserDialog } from '../coordinators/userCoordinator';
@@ -180,6 +181,7 @@
const { instanceJoinHistory } = storeToRefs(instanceStore); const { instanceJoinHistory } = storeToRefs(instanceStore);
const { canOpenInstanceInGame } = storeToRefs(inviteStore); const { canOpenInstanceInGame } = storeToRefs(inviteStore);
const { isOpeningInstance } = storeToRefs(launchStore); const { isOpeningInstance } = storeToRefs(launchStore);
const { checkCanInviteSelf } = useInviteChecks();
const props = defineProps({ const props = defineProps({
location: { location: {

View File

@@ -571,6 +571,7 @@
import { import {
useAppearanceSettingsStore, useAppearanceSettingsStore,
useAuthStore,
useAvatarStore, useAvatarStore,
useFavoriteStore, useFavoriteStore,
useGalleryStore, useGalleryStore,
@@ -622,6 +623,7 @@ import { showUserDialog } from '../../../coordinators/userCoordinator';
const { isGameRunning } = storeToRefs(useGameStore()); const { isGameRunning } = storeToRefs(useGameStore());
const { showFullscreenImageDialog } = useGalleryStore(); const { showFullscreenImageDialog } = useGalleryStore();
const { isDarkMode } = storeToRefs(useAppearanceSettingsStore()); const { isDarkMode } = storeToRefs(useAppearanceSettingsStore());
const authStore = useAuthStore();
const modalStore = useModalStore(); const modalStore = useModalStore();
const uiStore = useUiStore(); const uiStore = useUiStore();
@@ -698,7 +700,7 @@ import { showUserDialog } from '../../../coordinators/userCoordinator';
// skip imposters // skip imposters
continue; continue;
} }
if (!compareUnityVersion(unityPackage.unitySortNumber)) { if (!compareUnityVersion(unityPackage.unitySortNumber, authStore.cachedConfig.sdkUnityVersion)) {
continue; continue;
} }
let platform = 'PC'; let platform = 'PC';

View File

@@ -210,13 +210,15 @@
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { downloadAndSaveJson, hasGroupPermission, userImage } from '../../../shared/utils'; import { downloadAndSaveJson, hasGroupPermission } from '../../../shared/utils';
import { useUserDisplay } from '../../../composables/useUserDisplay';
import { useGroupStore, useUserStore } from '../../../stores'; import { useGroupStore, useUserStore } from '../../../stores';
import { applyGroupMember, handleGroupMember } from '../../../coordinators/groupCoordinator'; import { applyGroupMember, handleGroupMember } from '../../../coordinators/groupCoordinator';
import { groupDialogSortingOptions } from '../../../shared/constants'; import { groupDialogSortingOptions } from '../../../shared/constants';
import { useGroupMembers } from './useGroupMembers'; import { useGroupMembers } from './useGroupMembers';
import { showUserDialog } from '../../../coordinators/userCoordinator'; import { showUserDialog } from '../../../coordinators/userCoordinator';
const { userImage } = useUserDisplay();
const { t } = useI18n(); const { t } = useI18n();

View File

@@ -123,7 +123,8 @@
import { useAppearanceSettingsStore, useGalleryStore, useGroupStore, useUserStore } from '../../../stores'; import { useAppearanceSettingsStore, useGalleryStore, useGroupStore, useUserStore } from '../../../stores';
import { applyGroupMember, handleGroupMember, handleGroupMemberProps } from '../../../coordinators/groupCoordinator'; import { applyGroupMember, handleGroupMember, handleGroupMemberProps } from '../../../coordinators/groupCoordinator';
import { hasGroupPermission, userImage, userImageFull } from '../../../shared/utils'; import { hasGroupPermission } from '../../../shared/utils';
import { useUserDisplay } from '../../../composables/useUserDisplay';
import { groupDialogFilterOptions, groupDialogSortingOptions } from '../../../shared/constants'; import { groupDialogFilterOptions, groupDialogSortingOptions } from '../../../shared/constants';
import { groupRequest, userRequest } from '../../../api'; import { groupRequest, userRequest } from '../../../api';
import { resolveRoleNames } from './groupModerationUtils'; import { resolveRoleNames } from './groupModerationUtils';
@@ -142,6 +143,7 @@
import { showUserDialog } from '../../../coordinators/userCoordinator'; import { showUserDialog } from '../../../coordinators/userCoordinator';
// ── Stores ─────────────────────────────────────────────────── // ── Stores ───────────────────────────────────────────────────
const { userImage, userImageFull } = useUserDisplay();
const appearanceSettingsStore = useAppearanceSettingsStore(); const appearanceSettingsStore = useAppearanceSettingsStore();
const { randomUserColours } = storeToRefs(appearanceSettingsStore); const { randomUserColours } = storeToRefs(appearanceSettingsStore);

View File

@@ -106,12 +106,14 @@
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useFriendStore, useGalleryStore, useInviteStore, useModalStore, useUserStore } from '../../../stores'; import { useFriendStore, useGalleryStore, useInviteStore, useModalStore, useUserStore } from '../../../stores';
import { parseLocation, userImage, userStatusClass } from '../../../shared/utils'; import { parseLocation } from '../../../shared/utils';
import { useUserDisplay } from '../../../composables/useUserDisplay';
import { instanceRequest, notificationRequest } from '../../../api'; import { instanceRequest, notificationRequest } from '../../../api';
import { VirtualCombobox } from '../../ui/virtual-combobox'; import { VirtualCombobox } from '../../ui/virtual-combobox';
import SendInviteDialog from './SendInviteDialog.vue'; import SendInviteDialog from './SendInviteDialog.vue';
const { userImage, userStatusClass } = useUserDisplay();
const { vipFriends, onlineFriends, activeFriends } = storeToRefs(useFriendStore()); const { vipFriends, onlineFriends, activeFriends } = storeToRefs(useFriendStore());
const { refreshInviteMessageTableData } = useInviteStore(); const { refreshInviteMessageTableData } = useInviteStore();
const { currentUser } = storeToRefs(useUserStore()); const { currentUser } = storeToRefs(useUserStore());

View File

@@ -96,13 +96,15 @@
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { hasGroupPermission, userImage, userStatusClass } from '../../shared/utils'; import { hasGroupPermission } from '../../shared/utils';
import { useUserDisplay } from '../../composables/useUserDisplay';
import { useFriendStore, useGroupStore, useModalStore } from '../../stores'; import { useFriendStore, useGroupStore, useModalStore } from '../../stores';
import { groupRequest, queryRequest } from '../../api'; import { groupRequest, queryRequest } from '../../api';
import { VirtualCombobox } from '../ui/virtual-combobox'; import { VirtualCombobox } from '../ui/virtual-combobox';
import configRepository from '../../services/config'; import configRepository from '../../services/config';
const { userImage, userStatusClass } = useUserDisplay();
const { vipFriends, onlineFriends, activeFriends, offlineFriends } = storeToRefs(useFriendStore()); const { vipFriends, onlineFriends, activeFriends, offlineFriends } = storeToRefs(useFriendStore());
const { currentUserGroups, inviteGroupDialog } = storeToRefs(useGroupStore()); const { currentUserGroups, inviteGroupDialog } = storeToRefs(useGroupStore());
const { applyGroup } = useGroupStore(); const { applyGroup } = useGroupStore();

View File

@@ -161,7 +161,8 @@
useLocationStore, useLocationStore,
useModalStore useModalStore
} from '../../stores'; } from '../../stores';
import { checkCanInvite, getLaunchURL, isRealInstance, parseLocation } from '../../shared/utils'; import { getLaunchURL, isRealInstance, parseLocation } from '../../shared/utils';
import { useInviteChecks } from '../../composables/useInviteChecks';
import { instanceRequest, queryRequest } from '../../api'; import { instanceRequest, queryRequest } from '../../api';
import InviteDialog from './InviteDialog/InviteDialog.vue'; import InviteDialog from './InviteDialog/InviteDialog.vue';
@@ -178,6 +179,7 @@
const { canOpenInstanceInGame } = storeToRefs(useInviteStore()); const { canOpenInstanceInGame } = storeToRefs(useInviteStore());
const { isGameRunning } = storeToRefs(useGameStore()); const { isGameRunning } = storeToRefs(useGameStore());
const { checkCanInvite } = useInviteChecks();
const launchModeLabel = computed(() => const launchModeLabel = computed(() =>
launchDialog.value.desktop ? t('dialog.launch.start_as_desktop') : t('dialog.launch.launch') launchDialog.value.desktop ? t('dialog.launch.start_as_desktop') : t('dialog.launch.launch')

View File

@@ -63,11 +63,13 @@
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { hasGroupModerationPermission, userImage } from '../../shared/utils'; import { hasGroupModerationPermission } from '../../shared/utils';
import { useUserDisplay } from '../../composables/useUserDisplay';
import { VirtualCombobox } from '../ui/virtual-combobox'; import { VirtualCombobox } from '../ui/virtual-combobox';
import { queryRequest } from '../../api'; import { queryRequest } from '../../api';
import { useGroupStore } from '../../stores'; import { useGroupStore } from '../../stores';
const { userImage } = useUserDisplay();
const { currentUserGroups, moderateGroupDialog } = storeToRefs(useGroupStore()); const { currentUserGroups, moderateGroupDialog } = storeToRefs(useGroupStore());
const { showGroupMemberModerationDialog } = useGroupStore(); const { showGroupMemberModerationDialog } = useGroupStore();
const { t } = useI18n(); const { t } = useI18n();

View File

@@ -251,7 +251,7 @@
DropdownMenuTrigger DropdownMenuTrigger
} from '../../ui/dropdown-menu'; } from '../../ui/dropdown-menu';
import { useGameStore, useLocationStore, useUserStore } from '../../../stores'; import { useGameStore, useLocationStore, useUserStore } from '../../../stores';
import { checkCanInvite } from '../../../shared/utils'; import { useInviteChecks } from '../../../composables/useInviteChecks';
const props = defineProps({ const props = defineProps({
userDialogCommand: { userDialogCommand: {
@@ -265,6 +265,7 @@
const { userDialog, currentUser } = storeToRefs(useUserStore()); const { userDialog, currentUser } = storeToRefs(useUserStore());
const { isGameRunning } = storeToRefs(useGameStore()); const { isGameRunning } = storeToRefs(useGameStore());
const { lastLocation } = storeToRefs(useLocationStore()); const { lastLocation } = storeToRefs(useLocationStore());
const { checkCanInvite } = useInviteChecks();
const hasRequest = computed(() => userDialog.value.incomingRequest || userDialog.value.outgoingRequest); const hasRequest = computed(() => userDialog.value.incomingRequest || userDialog.value.outgoingRequest);
const hasRisk = computed( const hasRisk = computed(

View File

@@ -269,13 +269,14 @@
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../ui/dropdown-menu'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../ui/dropdown-menu';
import { useInstanceStore, useWorldStore } from '../../../stores'; import { useAuthStore, useInstanceStore, useWorldStore } from '../../../stores';
import { openExternalLink } from '../../../shared/utils'; import { openExternalLink } from '../../../shared/utils';
import { useWorldDialogInfo } from './useWorldDialogInfo'; import { useWorldDialogInfo } from './useWorldDialogInfo';
const { t } = useI18n(); const { t } = useI18n();
const { worldDialog } = storeToRefs(useWorldStore()); const { worldDialog } = storeToRefs(useWorldStore());
const authStore = useAuthStore();
const { showPreviousInstancesListDialog: openPreviousInstancesListDialog } = useInstanceStore(); const { showPreviousInstancesListDialog: openPreviousInstancesListDialog } = useInstanceStore();
const { const {
@@ -293,7 +294,7 @@
copyWorldName, copyWorldName,
commaNumber, commaNumber,
formatDateFilter formatDateFilter
} = useWorldDialogInfo(worldDialog, { t, toast }); } = useWorldDialogInfo(worldDialog, { t, toast, sdkUnityVersion: authStore.cachedConfig.sdkUnityVersion });
/** /**
* *

View File

@@ -108,7 +108,8 @@
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { refreshInstancePlayerCount, userImage, userStatusClass } from '../../../shared/utils'; import { refreshInstancePlayerCount } from '../../../shared/utils';
import { useUserDisplay } from '../../../composables/useUserDisplay';
import { import {
useAppearanceSettingsStore, useAppearanceSettingsStore,
useInstanceStore, useInstanceStore,
@@ -118,9 +119,10 @@
} from '../../../stores'; } from '../../../stores';
import InstanceActionBar from '../../InstanceActionBar.vue'; import InstanceActionBar from '../../InstanceActionBar.vue';
import { showUserDialog } from '../../../coordinators/userCoordinator'; import { showUserDialog } from '../../../coordinators/userCoordinator';
const { t } = useI18n(); const { t } = useI18n();
const { userImage, userStatusClass } = useUserDisplay();
const { isAgeGatedInstancesVisible } = storeToRefs(useAppearanceSettingsStore()); const { isAgeGatedInstancesVisible } = storeToRefs(useAppearanceSettingsStore());

View File

@@ -16,7 +16,7 @@ import { database } from '../../../services/database';
* @param {Function} deps.toast - toast notification function * @param {Function} deps.toast - toast notification function
* @returns {Object} info composable API * @returns {Object} info composable API
*/ */
export function useWorldDialogInfo(worldDialog, { t, toast }) { export function useWorldDialogInfo(worldDialog, { t, toast, sdkUnityVersion }) {
const memo = computed({ const memo = computed({
get() { get() {
return worldDialog.value.memo; return worldDialog.value.memo;
@@ -71,7 +71,7 @@ export function useWorldDialogInfo(worldDialog, { t, toast }) {
const platforms = []; const platforms = [];
if (ref.unityPackages) { if (ref.unityPackages) {
for (const unityPackage of ref.unityPackages) { for (const unityPackage of ref.unityPackages) {
if (!compareUnityVersion(unityPackage.unitySortNumber)) { if (!compareUnityVersion(unityPackage.unitySortNumber, sdkUnityVersion)) {
continue; continue;
} }
let platform = 'PC'; let platform = 'PC';

View File

@@ -0,0 +1,39 @@
import {
useFriendStore,
useInstanceStore,
useLocationStore,
useUserStore
} from '../stores';
import {
checkCanInvite as checkCanInvitePure,
checkCanInviteSelf as checkCanInviteSelfPure
} from '../shared/utils/invite';
/**
* Composable that provides store-aware invite check functions.
* Delegates to the pure utility functions after resolving store data.
*/
export function useInviteChecks() {
const userStore = useUserStore();
const locationStore = useLocationStore();
const instanceStore = useInstanceStore();
const friendStore = useFriendStore();
function checkCanInvite(location) {
return checkCanInvitePure(location, {
currentUserId: userStore.currentUser.id,
lastLocationStr: locationStore.lastLocation.location,
cachedInstances: instanceStore.cachedInstances
});
}
function checkCanInviteSelf(location) {
return checkCanInviteSelfPure(location, {
currentUserId: userStore.currentUser.id,
cachedInstances: instanceStore.cachedInstances,
friends: friendStore.friends
});
}
return { checkCanInvite, checkCanInviteSelf };
}

View File

@@ -0,0 +1,43 @@
import { useAppearanceSettingsStore, useUserStore } from '../stores';
import {
userImage as userImagePure,
userImageFull as userImageFullPure,
userStatusClass as userStatusClassPure
} from '../shared/utils/user';
/**
* Composable that provides store-aware user display functions.
* Delegates to the pure utility functions after resolving store data.
*/
export function useUserDisplay() {
const userStore = useUserStore();
const appearanceStore = useAppearanceSettingsStore();
function userStatusClass(user, pendingOffline = false) {
return userStatusClassPure(user, pendingOffline, userStore.currentUser);
}
function userImage(
user,
isIcon = false,
resolution = '128',
isUserDialogIcon = false
) {
return userImagePure(
user,
isIcon,
resolution,
isUserDialogIcon,
appearanceStore.displayVRCPlusIconsAsAvatar
);
}
function userImageFull(user) {
return userImageFullPure(
user,
appearanceStore.displayVRCPlusIconsAsAvatar
);
}
return { userStatusClass, userImage, userImageFull };
}

View File

@@ -321,7 +321,7 @@ export function updateUserCurrentStatus(ref) {
friendStore.updateOnlineFriendCounter(); friendStore.updateOnlineFriendCounter();
if (appearanceSettingsStore.randomUserColours) { if (appearanceSettingsStore.randomUserColours) {
getNameColour(userStore.currentUser.id).then((colour) => { getNameColour(userStore.currentUser.id, appearanceSettingsStore.isDarkMode).then((colour) => {
userStore.setCurrentUserColour(colour); userStore.setCurrentUserColour(colour);
}); });
} }

View File

@@ -1,151 +1,154 @@
import { beforeEach, describe, expect, test, vi } from 'vitest'; import { beforeEach, describe, expect, test, vi } from 'vitest';
// Mock stores
vi.mock('../../../stores', () => ({
useFriendStore: vi.fn(),
useInstanceStore: vi.fn(),
useLocationStore: vi.fn(),
useUserStore: vi.fn()
}));
// Mock transitive deps
vi.mock('../../../views/Feed/Feed.vue', () => ({
default: { template: '<div />' }
}));
vi.mock('../../../views/Feed/columns.jsx', () => ({ columns: [] }));
vi.mock('../../../plugins/router', () => ({
default: { push: vi.fn(), currentRoute: { value: {} } }
}));
import {
useFriendStore,
useInstanceStore,
useLocationStore,
useUserStore
} from '../../../stores';
import { checkCanInvite, checkCanInviteSelf } from '../invite'; import { checkCanInvite, checkCanInviteSelf } from '../invite';
const storeMocks = vi.hoisted(() => ({
useUserStore: vi.fn(() => ({ currentUser: { id: 'usr_me' } })),
useLocationStore: vi.fn(() => ({
lastLocation: { location: '', friendList: new Set() }
})),
useInstanceStore: vi.fn(() => ({ cachedInstances: new Map() })),
useFriendStore: vi.fn(() => ({ friends: new Map() }))
}));
vi.mock('../../../stores', () => storeMocks);
describe('Invite Utils', () => { describe('Invite Utils', () => {
beforeEach(() => { beforeEach(() => {
useUserStore.mockReturnValue({ vi.clearAllMocks();
currentUser: { id: 'usr_me' }
});
useLocationStore.mockReturnValue({
lastLocation: { location: 'wrld_last:12345' }
});
useInstanceStore.mockReturnValue({
cachedInstances: new Map()
});
useFriendStore.mockReturnValue({
friends: new Map()
});
}); });
const defaultInviteDeps = {
currentUserId: 'usr_me',
lastLocationStr: 'wrld_last:12345',
cachedInstances: new Map()
};
const defaultSelfDeps = {
currentUserId: 'usr_me',
cachedInstances: new Map(),
friends: new Map()
};
describe('checkCanInvite', () => { describe('checkCanInvite', () => {
test('does not access stores when deps are provided (pure path)', () => {
checkCanInvite('wrld_123:instance', defaultInviteDeps);
expect(storeMocks.useUserStore).not.toHaveBeenCalled();
expect(storeMocks.useLocationStore).not.toHaveBeenCalled();
expect(storeMocks.useInstanceStore).not.toHaveBeenCalled();
});
test('returns false for empty location', () => { test('returns false for empty location', () => {
expect(checkCanInvite('')).toBe(false); expect(checkCanInvite('', defaultInviteDeps)).toBe(false);
expect(checkCanInvite(null)).toBe(false); expect(checkCanInvite(null, defaultInviteDeps)).toBe(false);
}); });
test('returns true for public instance', () => { test('returns true for public instance', () => {
expect(checkCanInvite('wrld_123:instance')).toBe(true); expect(checkCanInvite('wrld_123:instance', defaultInviteDeps)).toBe(true);
}); });
test('returns true for group instance', () => { test('returns true for group instance', () => {
expect( expect(
checkCanInvite( checkCanInvite(
'wrld_123:instance~group(grp_123)~groupAccessType(public)' 'wrld_123:instance~group(grp_123)~groupAccessType(public)',
defaultInviteDeps
) )
).toBe(true); ).toBe(true);
}); });
test('returns true for own instance', () => { test('returns true for own instance', () => {
expect(checkCanInvite('wrld_123:instance~private(usr_me)')).toBe( expect(checkCanInvite('wrld_123:instance~private(usr_me)', defaultInviteDeps)).toBe(
true true
); );
}); });
test('returns false for invite-only instance owned by another', () => { test('returns false for invite-only instance owned by another', () => {
expect(checkCanInvite('wrld_123:instance~private(usr_other)')).toBe( expect(checkCanInvite('wrld_123:instance~private(usr_other)', defaultInviteDeps)).toBe(
false false
); );
}); });
test('returns false for friends-only instance', () => { test('returns false for friends-only instance', () => {
expect(checkCanInvite('wrld_123:instance~friends(usr_other)')).toBe( expect(checkCanInvite('wrld_123:instance~friends(usr_other)', defaultInviteDeps)).toBe(
false false
); );
}); });
test('returns true for friends+ instance if current location matches', () => { test('returns true for friends+ instance if current location matches', () => {
const location = 'wrld_123:instance~hidden(usr_other)'; const location = 'wrld_123:instance~hidden(usr_other)';
useLocationStore.mockReturnValue({ expect(checkCanInvite(location, {
lastLocation: { location } ...defaultInviteDeps,
}); lastLocationStr: location
expect(checkCanInvite(location)).toBe(true); })).toBe(true);
}); });
test('returns false for friends+ instance if not in that location', () => { test('returns false for friends+ instance if not in that location', () => {
expect(checkCanInvite('wrld_123:instance~hidden(usr_other)')).toBe( expect(checkCanInvite('wrld_123:instance~hidden(usr_other)', defaultInviteDeps)).toBe(
false false
); );
}); });
test('returns false for closed instance', () => { test('returns false for closed instance', () => {
const location = 'wrld_123:instance'; const location = 'wrld_123:instance';
useInstanceStore.mockReturnValue({ expect(checkCanInvite(location, {
...defaultInviteDeps,
cachedInstances: new Map([ cachedInstances: new Map([
[location, { closedAt: '2024-01-01' }] [location, { closedAt: '2024-01-01' }]
]) ])
}); })).toBe(false);
expect(checkCanInvite(location)).toBe(false);
}); });
}); });
describe('checkCanInviteSelf', () => { describe('checkCanInviteSelf', () => {
test('does not access stores when deps are provided (pure path)', () => {
checkCanInviteSelf('wrld_123:instance', defaultSelfDeps);
expect(storeMocks.useUserStore).not.toHaveBeenCalled();
expect(storeMocks.useInstanceStore).not.toHaveBeenCalled();
expect(storeMocks.useFriendStore).not.toHaveBeenCalled();
});
test('returns false for empty location', () => { test('returns false for empty location', () => {
expect(checkCanInviteSelf('')).toBe(false); expect(checkCanInviteSelf('', defaultSelfDeps)).toBe(false);
expect(checkCanInviteSelf(null)).toBe(false); expect(checkCanInviteSelf(null, defaultSelfDeps)).toBe(false);
}); });
test('returns true for own instance', () => { test('returns true for own instance', () => {
expect( expect(
checkCanInviteSelf('wrld_123:instance~private(usr_me)') checkCanInviteSelf('wrld_123:instance~private(usr_me)', defaultSelfDeps)
).toBe(true); ).toBe(true);
}); });
test('returns true for public instance', () => { test('returns true for public instance', () => {
expect(checkCanInviteSelf('wrld_123:instance')).toBe(true); expect(checkCanInviteSelf('wrld_123:instance', defaultSelfDeps)).toBe(true);
}); });
test('returns true for friends-only instance if user is a friend', () => { test('returns true for friends-only instance if user is a friend', () => {
useFriendStore.mockReturnValue({
friends: new Map([['usr_owner', {}]])
});
expect( expect(
checkCanInviteSelf('wrld_123:instance~friends(usr_owner)') checkCanInviteSelf('wrld_123:instance~friends(usr_owner)', {
...defaultSelfDeps,
friends: new Map([['usr_owner', {}]])
})
).toBe(true); ).toBe(true);
}); });
test('returns false for friends-only instance if user is not a friend', () => { test('returns false for friends-only instance if user is not a friend', () => {
expect( expect(
checkCanInviteSelf('wrld_123:instance~friends(usr_other)') checkCanInviteSelf('wrld_123:instance~friends(usr_other)', defaultSelfDeps)
).toBe(false); ).toBe(false);
}); });
test('returns false for closed instance', () => { test('returns false for closed instance', () => {
const location = 'wrld_123:instance'; const location = 'wrld_123:instance';
useInstanceStore.mockReturnValue({ expect(checkCanInviteSelf(location, {
...defaultSelfDeps,
cachedInstances: new Map([ cachedInstances: new Map([
[location, { closedAt: '2024-01-01' }] [location, { closedAt: '2024-01-01' }]
]) ])
}); })).toBe(false);
expect(checkCanInviteSelf(location)).toBe(false);
}); });
test('returns true for invite instance (not owned, not closed)', () => { test('returns true for invite instance (not owned, not closed)', () => {
expect( expect(
checkCanInviteSelf('wrld_123:instance~private(usr_other)') checkCanInviteSelf('wrld_123:instance~private(usr_other)', defaultSelfDeps)
).toBe(true); ).toBe(true);
}); });
}); });

View File

@@ -1,11 +1,5 @@
import { beforeEach, describe, expect, test, vi } from 'vitest'; import { beforeEach, describe, expect, test, vi } from 'vitest';
// Mock stores
vi.mock('../../../stores', () => ({
useUserStore: vi.fn(),
useAppearanceSettingsStore: vi.fn()
}));
// Mock common.js // Mock common.js
vi.mock('../common', () => ({ vi.mock('../common', () => ({
convertFileUrlToImageUrl: vi.fn((url) => `converted:${url}`) convertFileUrlToImageUrl: vi.fn((url) => `converted:${url}`)
@@ -21,16 +15,22 @@ vi.mock('../base/ui', () => ({
HueToHex: vi.fn((h) => `#hue${h}`) HueToHex: vi.fn((h) => `#hue${h}`)
})); }));
// Mock transitive deps that get pulled in via stores const storeMocks = vi.hoisted(() => ({
vi.mock('../../../views/Feed/Feed.vue', () => ({ useUserStore: vi.fn(() => ({
default: { template: '<div />' } currentUser: {
})); id: 'usr_store',
vi.mock('../../../views/Feed/columns.jsx', () => ({ columns: [] })); presence: { platform: 'standalonewindows' },
vi.mock('../../../plugins/router', () => ({ onlineFriends: [],
default: { push: vi.fn(), currentRoute: { value: {} } } activeFriends: []
}
})),
useAppearanceSettingsStore: vi.fn(() => ({
displayVRCPlusIconsAsAvatar: false
}))
})); }));
import { useAppearanceSettingsStore, useUserStore } from '../../../stores'; vi.mock('../../../stores', () => storeMocks);
import { import {
languageClass, languageClass,
parseUserUrl, parseUserUrl,
@@ -209,28 +209,42 @@ describe('User Utils', () => {
}); });
}); });
describe('userStatusClass (with store mock)', () => { describe('userStatusClass (explicit currentUser)', () => {
let currentUser;
beforeEach(() => { beforeEach(() => {
useUserStore.mockReturnValue({ vi.clearAllMocks();
currentUser: { currentUser = {
id: 'usr_me', id: 'usr_me',
presence: { platform: 'standalonewindows' }, presence: { platform: 'standalonewindows' },
onlineFriends: [], onlineFriends: [],
activeFriends: [] activeFriends: []
} };
}); });
test('does not access stores when currentUser is passed (pure path)', () => {
userStatusClass(
{ id: 'usr_me', status: 'active', isFriend: true },
false,
currentUser
);
expect(storeMocks.useUserStore).not.toHaveBeenCalled();
}); });
test('returns null for undefined user', () => { test('returns null for undefined user', () => {
expect(userStatusClass(undefined)).toBeNull(); expect(userStatusClass(undefined, false, currentUser)).toBeNull();
}); });
test('returns current user style with status', () => { test('returns current user style with status', () => {
const result = userStatusClass({ const result = userStatusClass(
id: 'usr_me', {
status: 'active', id: 'usr_me',
isFriend: true status: 'active',
}); isFriend: true
},
false,
currentUser
);
expect(result).toMatchObject({ expect(result).toMatchObject({
'status-icon': true, 'status-icon': true,
online: true, online: true,
@@ -239,35 +253,37 @@ describe('User Utils', () => {
}); });
test('returns mobile true for non-PC platform on current user', () => { test('returns mobile true for non-PC platform on current user', () => {
useUserStore.mockReturnValue({ currentUser.presence = { platform: 'android' };
currentUser: { const result = userStatusClass(
{
id: 'usr_me', id: 'usr_me',
presence: { platform: 'android' }, status: 'active'
onlineFriends: [], },
activeFriends: [] false,
} currentUser
}); );
const result = userStatusClass({
id: 'usr_me',
status: 'active'
});
expect(result.mobile).toBe(true); expect(result.mobile).toBe(true);
}); });
test('returns null for non-friend users', () => { test('returns null for non-friend users', () => {
expect( expect(
userStatusClass({ userStatusClass(
id: 'usr_other', {
status: 'active', id: 'usr_other',
isFriend: false status: 'active',
}) isFriend: false
},
false,
currentUser
)
).toBeNull(); ).toBeNull();
}); });
test('returns offline style for pending offline friend', () => { test('returns offline style for pending offline friend', () => {
const result = userStatusClass( const result = userStatusClass(
{ id: 'usr_other', isFriend: true, status: 'active' }, { id: 'usr_other', isFriend: true, status: 'active' },
true true,
currentUser
); );
expect(result).toMatchObject({ expect(result).toMatchObject({
'status-icon': true, 'status-icon': true,
@@ -309,7 +325,7 @@ describe('User Utils', () => {
status, status,
location, location,
state state
}); }, false, currentUser);
expect(result[expected]).toBe(true); expect(result[expected]).toBe(true);
} }
}); });
@@ -321,7 +337,7 @@ describe('User Utils', () => {
status: 'active', status: 'active',
location: 'offline', location: 'offline',
state: '' state: ''
}); }, false, currentUser);
expect(result.offline).toBe(true); expect(result.offline).toBe(true);
}); });
@@ -332,7 +348,7 @@ describe('User Utils', () => {
status: 'busy', status: 'busy',
location: 'private', location: 'private',
state: 'active' state: 'active'
}); }, false, currentUser);
expect(result.active).toBe(true); expect(result.active).toBe(true);
}); });
@@ -344,7 +360,7 @@ describe('User Utils', () => {
location: 'wrld_1', location: 'wrld_1',
state: 'online', state: 'online',
$platform: 'android' $platform: 'android'
}); }, false, currentUser);
expect(result.mobile).toBe(true); expect(result.mobile).toBe(true);
}); });
@@ -356,7 +372,7 @@ describe('User Utils', () => {
location: 'wrld_1', location: 'wrld_1',
state: 'online', state: 'online',
$platform: 'standalonewindows' $platform: 'standalonewindows'
}); }, false, currentUser);
expect(result.mobile).toBeUndefined(); expect(result.mobile).toBeUndefined();
}); });
@@ -364,7 +380,7 @@ describe('User Utils', () => {
const result = userStatusClass({ const result = userStatusClass({
userId: 'usr_me', userId: 'usr_me',
status: 'busy' status: 'busy'
}); }, false, currentUser);
expect(result).toMatchObject({ expect(result).toMatchObject({
'status-icon': true, 'status-icon': true,
busy: true, busy: true,
@@ -373,79 +389,78 @@ describe('User Utils', () => {
}); });
test('handles private location with empty state (temp fix branch)', () => { test('handles private location with empty state (temp fix branch)', () => {
useUserStore.mockReturnValue({ currentUser.activeFriends = ['usr_f'];
currentUser: {
id: 'usr_me',
onlineFriends: [],
activeFriends: ['usr_f']
}
});
const result = userStatusClass({ const result = userStatusClass({
id: 'usr_f', id: 'usr_f',
isFriend: true, isFriend: true,
status: 'busy', status: 'busy',
location: 'private', location: 'private',
state: '' state: ''
}); }, false, currentUser);
// activeFriends includes usr_f → active // activeFriends includes usr_f → active
expect(result.active).toBe(true); expect(result.active).toBe(true);
}); });
test('handles private location temp fix → offline branch', () => { test('handles private location temp fix → offline branch', () => {
useUserStore.mockReturnValue({ currentUser.activeFriends = [];
currentUser: {
id: 'usr_me',
onlineFriends: [],
activeFriends: []
}
});
const result = userStatusClass({ const result = userStatusClass({
id: 'usr_f', id: 'usr_f',
isFriend: true, isFriend: true,
status: 'busy', status: 'busy',
location: 'private', location: 'private',
state: '' state: ''
}); }, false, currentUser);
expect(result.offline).toBe(true); expect(result.offline).toBe(true);
}); });
}); });
describe('userImage (with store mock)', () => { describe('userImage (explicit settings)', () => {
beforeEach(() => { test('does not access appearance store when setting is passed (pure path)', () => {
useAppearanceSettingsStore.mockReturnValue({ userImage(
displayVRCPlusIconsAsAvatar: false { thumbnailUrl: 'https://img.com/thumb' },
}); false,
'128',
false,
false
);
expect(storeMocks.useAppearanceSettingsStore).not.toHaveBeenCalled();
}); });
test('returns empty string for falsy user', () => { test('returns empty string for falsy user', () => {
expect(userImage(null)).toBe(''); expect(userImage(null, false, '128', false, false)).toBe('');
expect(userImage(undefined)).toBe(''); expect(userImage(undefined, false, '128', false, false)).toBe('');
}); });
test('returns profilePicOverrideThumbnail when available', () => { test('returns profilePicOverrideThumbnail when available', () => {
const user = { const user = {
profilePicOverrideThumbnail: 'https://img.com/pic/256/thumb' profilePicOverrideThumbnail: 'https://img.com/pic/256/thumb'
}; };
expect(userImage(user)).toBe('https://img.com/pic/256/thumb'); expect(userImage(user, false, '128', false, false)).toBe(
'https://img.com/pic/256/thumb'
);
}); });
test('replaces resolution for icon mode with profilePicOverrideThumbnail', () => { test('replaces resolution for icon mode with profilePicOverrideThumbnail', () => {
const user = { const user = {
profilePicOverrideThumbnail: 'https://img.com/pic/256/thumb' profilePicOverrideThumbnail: 'https://img.com/pic/256/thumb'
}; };
expect(userImage(user, true, '64')).toBe( expect(userImage(user, true, '64', false, false)).toBe(
'https://img.com/pic/64/thumb' 'https://img.com/pic/64/thumb'
); );
}); });
test('returns profilePicOverride when no thumbnail', () => { test('returns profilePicOverride when no thumbnail', () => {
const user = { profilePicOverride: 'https://img.com/full' }; const user = { profilePicOverride: 'https://img.com/full' };
expect(userImage(user)).toBe('https://img.com/full'); expect(userImage(user, false, '128', false, false)).toBe(
'https://img.com/full'
);
}); });
test('returns thumbnailUrl as fallback', () => { test('returns thumbnailUrl as fallback', () => {
const user = { thumbnailUrl: 'https://img.com/thumb' }; const user = { thumbnailUrl: 'https://img.com/thumb' };
expect(userImage(user)).toBe('https://img.com/thumb'); expect(userImage(user, false, '128', false, false)).toBe(
'https://img.com/thumb'
);
}); });
test('returns currentAvatarThumbnailImageUrl as fallback', () => { test('returns currentAvatarThumbnailImageUrl as fallback', () => {
@@ -453,7 +468,9 @@ describe('User Utils', () => {
currentAvatarThumbnailImageUrl: currentAvatarThumbnailImageUrl:
'https://img.com/avatar/256/thumb' 'https://img.com/avatar/256/thumb'
}; };
expect(userImage(user)).toBe('https://img.com/avatar/256/thumb'); expect(userImage(user, false, '128', false, false)).toBe(
'https://img.com/avatar/256/thumb'
);
}); });
test('replaces resolution for icon mode with currentAvatarThumbnailImageUrl', () => { test('replaces resolution for icon mode with currentAvatarThumbnailImageUrl', () => {
@@ -461,7 +478,7 @@ describe('User Utils', () => {
currentAvatarThumbnailImageUrl: currentAvatarThumbnailImageUrl:
'https://img.com/avatar/256/thumb' 'https://img.com/avatar/256/thumb'
}; };
expect(userImage(user, true, '64')).toBe( expect(userImage(user, true, '64', false, false)).toBe(
'https://img.com/avatar/64/thumb' 'https://img.com/avatar/64/thumb'
); );
}); });
@@ -470,39 +487,37 @@ describe('User Utils', () => {
const user = { const user = {
currentAvatarImageUrl: 'https://img.com/avatar/full' currentAvatarImageUrl: 'https://img.com/avatar/full'
}; };
expect(userImage(user)).toBe('https://img.com/avatar/full'); expect(userImage(user, false, '128', false, false)).toBe(
'https://img.com/avatar/full'
);
}); });
test('converts currentAvatarImageUrl for icon mode', () => { test('converts currentAvatarImageUrl for icon mode', () => {
const user = { const user = {
currentAvatarImageUrl: 'https://img.com/avatar/full' currentAvatarImageUrl: 'https://img.com/avatar/full'
}; };
expect(userImage(user, true)).toBe( expect(userImage(user, true, '128', false, false)).toBe(
'converted:https://img.com/avatar/full' 'converted:https://img.com/avatar/full'
); );
}); });
test('returns empty string when user has no image fields', () => { test('returns empty string when user has no image fields', () => {
expect(userImage({})).toBe(''); expect(userImage({}, false, '128', false, false)).toBe('');
}); });
test('returns userIcon when displayVRCPlusIconsAsAvatar is true', () => { test('returns userIcon when displayVRCPlusIconsAsAvatar is true', () => {
useAppearanceSettingsStore.mockReturnValue({
displayVRCPlusIconsAsAvatar: true
});
const user = { const user = {
userIcon: 'https://img.com/icon', userIcon: 'https://img.com/icon',
thumbnailUrl: 'https://img.com/thumb' thumbnailUrl: 'https://img.com/thumb'
}; };
expect(userImage(user)).toBe('https://img.com/icon'); expect(userImage(user, false, '128', false, true)).toBe(
'https://img.com/icon'
);
}); });
test('converts userIcon for icon mode when VRCPlus setting enabled', () => { test('converts userIcon for icon mode when VRCPlus setting enabled', () => {
useAppearanceSettingsStore.mockReturnValue({
displayVRCPlusIconsAsAvatar: true
});
const user = { userIcon: 'https://img.com/icon' }; const user = { userIcon: 'https://img.com/icon' };
expect(userImage(user, true)).toBe( expect(userImage(user, true, '128', false, true)).toBe(
'converted:https://img.com/icon' 'converted:https://img.com/icon'
); );
}); });
@@ -512,21 +527,23 @@ describe('User Utils', () => {
userIcon: 'https://img.com/icon', userIcon: 'https://img.com/icon',
thumbnailUrl: 'https://img.com/thumb' thumbnailUrl: 'https://img.com/thumb'
}; };
expect(userImage(user, false, '128', true)).toBe( expect(userImage(user, false, '128', true, false)).toBe(
'https://img.com/icon' 'https://img.com/icon'
); );
}); });
}); });
describe('userImageFull (with store mock)', () => { describe('userImageFull (explicit settings)', () => {
beforeEach(() => { test('does not access appearance store when setting is passed (pure path)', () => {
useAppearanceSettingsStore.mockReturnValue({ userImageFull(
displayVRCPlusIconsAsAvatar: false { currentAvatarImageUrl: 'https://img.com/avatar' },
}); false
);
expect(storeMocks.useAppearanceSettingsStore).not.toHaveBeenCalled();
}); });
test('returns empty string for falsy user', () => { test('returns empty string for falsy user', () => {
expect(userImageFull(null)).toBe(''); expect(userImageFull(null, false)).toBe('');
}); });
test('returns profilePicOverride when available', () => { test('returns profilePicOverride when available', () => {
@@ -534,25 +551,22 @@ describe('User Utils', () => {
profilePicOverride: 'https://img.com/full', profilePicOverride: 'https://img.com/full',
currentAvatarImageUrl: 'https://img.com/avatar' currentAvatarImageUrl: 'https://img.com/avatar'
}; };
expect(userImageFull(user)).toBe('https://img.com/full'); expect(userImageFull(user, false)).toBe('https://img.com/full');
}); });
test('returns currentAvatarImageUrl as fallback', () => { test('returns currentAvatarImageUrl as fallback', () => {
const user = { const user = {
currentAvatarImageUrl: 'https://img.com/avatar' currentAvatarImageUrl: 'https://img.com/avatar'
}; };
expect(userImageFull(user)).toBe('https://img.com/avatar'); expect(userImageFull(user, false)).toBe('https://img.com/avatar');
}); });
test('returns userIcon when VRCPlus setting enabled', () => { test('returns userIcon when VRCPlus setting enabled', () => {
useAppearanceSettingsStore.mockReturnValue({
displayVRCPlusIconsAsAvatar: true
});
const user = { const user = {
userIcon: 'https://img.com/icon', userIcon: 'https://img.com/icon',
profilePicOverride: 'https://img.com/full' profilePicOverride: 'https://img.com/full'
}; };
expect(userImageFull(user)).toBe('https://img.com/icon'); expect(userImageFull(user, true)).toBe('https://img.com/icon');
}); });
}); });
}); });

View File

@@ -1,5 +1,4 @@
import { replaceBioSymbols } from './base/string'; import { replaceBioSymbols } from './base/string';
import { useAuthStore } from '../../stores';
/** /**
* *
@@ -95,10 +94,9 @@ function getPlatformInfo(unityPackages) {
* @param {string} unitySortNumber * @param {string} unitySortNumber
* @returns {boolean} * @returns {boolean}
*/ */
function compareUnityVersion(unitySortNumber) { function compareUnityVersion(unitySortNumber, sdkUnityVersion) {
const authStore = useAuthStore(); if (!sdkUnityVersion) {
if (!authStore.cachedConfig.sdkUnityVersion) { console.error('No sdkUnityVersion provided');
console.error('No cachedConfig.sdkUnityVersion');
return false; return false;
} }
@@ -106,9 +104,9 @@ function compareUnityVersion(unitySortNumber) {
// 2019.4.31f1 2019 04 31 000 // 2019.4.31f1 2019 04 31 000
// 5.3.4p1 5 03 04 010 // 5.3.4p1 5 03 04 010
// 2019.4.31f1c1 is a thing // 2019.4.31f1c1 is a thing
const array = authStore.cachedConfig.sdkUnityVersion.split('.'); const array = sdkUnityVersion.split('.');
if (array.length < 3) { if (array.length < 3) {
console.error('Invalid cachedConfig.sdkUnityVersion'); console.error('Invalid sdkUnityVersion');
return false; return false;
} }
let currentUnityVersion = array[0]; let currentUnityVersion = array[0];

View File

@@ -3,7 +3,7 @@ import {
extractFileVersion, extractFileVersion,
extractVariantVersion extractVariantVersion
} from '../common'; } from '../common';
import { useAvatarStore, useWorldStore } from '../../../stores'; import { useAuthStore, useAvatarStore, useWorldStore } from '../../../stores';
import { compareUnityVersion } from '../avatar'; import { compareUnityVersion } from '../avatar';
/** /**
@@ -12,6 +12,8 @@ import { compareUnityVersion } from '../avatar';
* @returns {Promise<string|null>} * @returns {Promise<string|null>}
*/ */
async function getBundleLocation(input) { async function getBundleLocation(input) {
const authStore = useAuthStore();
const sdkUnityVersion = authStore.cachedConfig.sdkUnityVersion;
const worldStore = useWorldStore(); const worldStore = useWorldStore();
const avatarStore = useAvatarStore(); const avatarStore = useAvatarStore();
let unityPackage; let unityPackage;
@@ -36,7 +38,7 @@ async function getBundleLocation(input) {
} }
if ( if (
unityPackage.platform === 'standalonewindows' && unityPackage.platform === 'standalonewindows' &&
compareUnityVersion(unityPackage.unitySortNumber) compareUnityVersion(unityPackage.unitySortNumber, sdkUnityVersion)
) { ) {
assetUrl = unityPackage.assetUrl; assetUrl = unityPackage.assetUrl;
if (unityPackage.variant !== 'standard') { if (unityPackage.variant !== 'standard') {
@@ -59,7 +61,7 @@ async function getBundleLocation(input) {
unityPackage = unityPackages[i]; unityPackage = unityPackages[i];
if ( if (
unityPackage.platform === 'standalonewindows' && unityPackage.platform === 'standalonewindows' &&
compareUnityVersion(unityPackage.unitySortNumber) compareUnityVersion(unityPackage.unitySortNumber, sdkUnityVersion)
) { ) {
assetUrl = unityPackage.assetUrl; assetUrl = unityPackage.assetUrl;
break; break;

View File

@@ -1,5 +1,4 @@
import { ref } from 'vue'; import { ref } from 'vue';
import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { import {
@@ -11,7 +10,6 @@ import {
import { i18n } from '../../../plugins/i18n'; import { i18n } from '../../../plugins/i18n';
import { router } from '../../../plugins/router'; import { router } from '../../../plugins/router';
import { textToHex } from './string'; import { textToHex } from './string';
import { useAppearanceSettingsStore } from '../../../stores';
import configRepository from '../../../services/config.js'; import configRepository from '../../../services/config.js';
@@ -320,11 +318,9 @@ async function refreshCustomScript() {
* @param {number} hue * @param {number} hue
* @returns {string} * @returns {string}
*/ */
function HueToHex(hue) { function HueToHex(hue, isDarkMode) {
const appSettingsStore = useAppearanceSettingsStore();
const { isDarkMode } = storeToRefs(appSettingsStore);
// this.HSVtoRGB(hue / 65535, .8, .8); // this.HSVtoRGB(hue / 65535, .8, .8);
if (isDarkMode.value) { if (isDarkMode) {
return HSVtoRGB(hue / 65535, 0.6, 1); return HSVtoRGB(hue / 65535, 0.6, 1);
} }
return HSVtoRGB(hue / 65535, 1, 0.7); return HSVtoRGB(hue / 65535, 1, 0.7);

View File

@@ -2,6 +2,7 @@ import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { import {
useAuthStore,
useAvatarStore, useAvatarStore,
useInstanceStore, useInstanceStore,
useModalStore, useModalStore,
@@ -47,6 +48,8 @@ function downloadAndSaveJson(fileName, data) {
} }
async function deleteVRChatCache(ref) { async function deleteVRChatCache(ref) {
const authStore = useAuthStore();
const sdkUnityVersion = authStore.cachedConfig.sdkUnityVersion;
let assetUrl = ''; let assetUrl = '';
let variant = ''; let variant = '';
for (let i = ref.unityPackages.length - 1; i > -1; i--) { for (let i = ref.unityPackages.length - 1; i > -1; i--) {
@@ -60,7 +63,7 @@ async function deleteVRChatCache(ref) {
} }
if ( if (
unityPackage.platform === 'standalonewindows' && unityPackage.platform === 'standalonewindows' &&
compareUnityVersion(unityPackage.unitySortNumber) compareUnityVersion(unityPackage.unitySortNumber, sdkUnityVersion)
) { ) {
assetUrl = unityPackage.assetUrl; assetUrl = unityPackage.assetUrl;
if (!unityPackage.variant || unityPackage.variant === 'standard') { if (!unityPackage.variant || unityPackage.variant === 'standard') {
@@ -86,6 +89,8 @@ async function checkVRChatCache(ref) {
if (!ref.unityPackages) { if (!ref.unityPackages) {
return { Item1: -1, Item2: false, Item3: '' }; return { Item1: -1, Item2: false, Item3: '' };
} }
const authStore = useAuthStore();
const sdkUnityVersion = authStore.cachedConfig.sdkUnityVersion;
let assetUrl = ''; let assetUrl = '';
let variant = ''; let variant = '';
for (let i = ref.unityPackages.length - 1; i > -1; i--) { for (let i = ref.unityPackages.length - 1; i > -1; i--) {
@@ -95,7 +100,7 @@ async function checkVRChatCache(ref) {
} }
if ( if (
unityPackage.platform === 'standalonewindows' && unityPackage.platform === 'standalonewindows' &&
compareUnityVersion(unityPackage.unitySortNumber) compareUnityVersion(unityPackage.unitySortNumber, sdkUnityVersion)
) { ) {
assetUrl = unityPackage.assetUrl; assetUrl = unityPackage.assetUrl;
if (!unityPackage.variant || unityPackage.variant === 'standard') { if (!unityPackage.variant || unityPackage.variant === 'standard') {
@@ -153,7 +158,7 @@ function copyToClipboard(text, message = 'Copied successfully!') {
* @param {number} resolution * @param {number} resolution
* @returns {string} * @returns {string}
*/ */
function convertFileUrlToImageUrl(url, resolution = 128) { function convertFileUrlToImageUrl(url, resolution = 128, endpointDomain = AppDebug.endpointDomain) {
if (!url) { if (!url) {
return ''; return '';
} }
@@ -170,7 +175,7 @@ function convertFileUrlToImageUrl(url, resolution = 128) {
if (match) { if (match) {
const fileId = match[1]; const fileId = match[1];
const version = match[2]; const version = match[2];
return `${AppDebug.endpointDomain}/image/file_${fileId}/${version}/${resolution}`; return `${endpointDomain}/image/file_${fileId}/${version}/${resolution}`;
} }
// no match return origin url // no match return origin url
return url; return url;
@@ -223,6 +228,8 @@ function openDiscordProfile(discordId) {
* @returns {Promise<object>} * @returns {Promise<object>}
*/ */
async function getBundleDateSize(ref) { async function getBundleDateSize(ref) {
const authStore = useAuthStore();
const sdkUnityVersion = authStore.cachedConfig.sdkUnityVersion;
const avatarStore = useAvatarStore(); const avatarStore = useAvatarStore();
const { avatarDialog } = storeToRefs(avatarStore); const { avatarDialog } = storeToRefs(avatarStore);
const worldStore = useWorldStore(); const worldStore = useWorldStore();
@@ -243,7 +250,7 @@ async function getBundleDateSize(ref) {
) { ) {
continue; continue;
} }
if (!compareUnityVersion(unityPackage.unitySortNumber)) { if (!compareUnityVersion(unityPackage.unitySortNumber, sdkUnityVersion)) {
continue; continue;
} }

View File

@@ -1,39 +1,34 @@
import {
useFriendStore,
useInstanceStore,
useLocationStore,
useUserStore
} from '../../stores';
import { parseLocation } from './location'; import { parseLocation } from './location';
/** /**
* *
* @param {string} location * @param {string} location
* @returns * @param {object} deps
* @param {string} deps.currentUserId - current user's id
* @param {string} deps.lastLocationStr - last location string from location store
* @param {Map} deps.cachedInstances - instance cache map
* @returns {boolean}
*/ */
function checkCanInvite(location) { function checkCanInvite(location, deps) {
if (!location) { if (!location) {
return false; return false;
} }
const userStore = useUserStore();
const locationStore = useLocationStore();
const instanceStore = useInstanceStore();
const L = parseLocation(location); const L = parseLocation(location);
const instance = instanceStore.cachedInstances.get(location); const instance = deps.cachedInstances?.get(location);
if (instance?.closedAt) { if (instance?.closedAt) {
return false; return false;
} }
if ( if (
L.accessType === 'public' || L.accessType === 'public' ||
L.accessType === 'group' || L.accessType === 'group' ||
L.userId === userStore.currentUser.id L.userId === deps.currentUserId
) { ) {
return true; return true;
} }
if (L.accessType === 'invite' || L.accessType === 'friends') { if (L.accessType === 'invite' || L.accessType === 'friends') {
return false; return false;
} }
if (locationStore.lastLocation.location === location) { if (deps.lastLocationStr === location) {
return true; return true;
} }
return false; return false;
@@ -42,24 +37,25 @@ function checkCanInvite(location) {
/** /**
* *
* @param {string} location * @param {string} location
* @returns * @param {object} deps
* @param {string} deps.currentUserId - current user's id
* @param {Map} deps.cachedInstances - instance cache map
* @param {Map} deps.friends - friends map
* @returns {boolean}
*/ */
function checkCanInviteSelf(location) { function checkCanInviteSelf(location, deps) {
if (!location) { if (!location) {
return false; return false;
} }
const userStore = useUserStore();
const instanceStore = useInstanceStore();
const friendStore = useFriendStore();
const L = parseLocation(location); const L = parseLocation(location);
const instance = instanceStore.cachedInstances.get(location); const instance = deps.cachedInstances?.get(location);
if (instance?.closedAt) { if (instance?.closedAt) {
return false; return false;
} }
if (L.userId === userStore.currentUser.id) { if (L.userId === deps.currentUserId) {
return true; return true;
} }
if (L.accessType === 'friends' && !friendStore.friends.has(L.userId)) { if (L.accessType === 'friends' && !deps.friends?.has(L.userId)) {
return false; return false;
} }
return true; return true;

View File

@@ -1,5 +1,4 @@
import { isRealInstance } from './instance.js'; import { isRealInstance } from './instance.js';
import { useLocationStore } from '../../stores/location.js';
export { export {
parseLocation, parseLocation,
@@ -10,10 +9,12 @@ export {
/** /**
* *
* @param friendsArr * @param {Array} friendsArr
* @param {object} lastLocation - last location from location store
* @param {Set} lastLocation.friendList
* @param {string} lastLocation.location
*/ */
function getFriendsLocations(friendsArr) { function getFriendsLocations(friendsArr, lastLocation) {
const locationStore = useLocationStore();
// prevent the instance title display as "Traveling". // prevent the instance title display as "Traveling".
if (!friendsArr?.length) { if (!friendsArr?.length) {
return ''; return '';
@@ -28,9 +29,11 @@ function getFriendsLocations(friendsArr) {
return friend.ref.travelingToLocation; return friend.ref.travelingToLocation;
} }
} }
for (const friend of friendsArr) { if (lastLocation) {
if (locationStore.lastLocation.friendList.has(friend.id)) { for (const friend of friendsArr) {
return locationStore.lastLocation.location; if (lastLocation.friendList.has(friend.id)) {
return lastLocation.location;
}
} }
} }
return friendsArr[0].ref?.location; return friendsArr[0].ref?.location;

View File

@@ -1,4 +1,3 @@
import { useAppearanceSettingsStore, useUserStore } from '../../stores';
import { HueToHex } from './base/ui'; import { HueToHex } from './base/ui';
import { convertFileUrlToImageUrl } from './common'; import { convertFileUrlToImageUrl } from './common';
import { languageMappings } from '../constants'; import { languageMappings } from '../constants';
@@ -40,21 +39,22 @@ function languageClass(language) {
/** /**
* *
* @param {string} userId * @param {string} userId
* @param {boolean} isDarkMode
* @returns * @returns
*/ */
async function getNameColour(userId) { async function getNameColour(userId, isDarkMode) {
const hue = await AppApi.GetColourFromUserID(userId); const hue = await AppApi.GetColourFromUserID(userId);
return HueToHex(hue); return HueToHex(hue, isDarkMode);
} }
/** /**
* *
* @param {object} user * @param {object} user
* @param {boolean} pendingOffline * @param {boolean} pendingOffline
* @param {object} currentUser - current user object from useUserStore
* @returns * @returns
*/ */
function userStatusClass(user, pendingOffline = false) { function userStatusClass(user, pendingOffline = false, currentUser) {
const userStore = useUserStore();
const style = { const style = {
'status-icon': true 'status-icon': true
}; };
@@ -67,8 +67,8 @@ function userStatusClass(user, pendingOffline = false) {
} else if (user.userId) { } else if (user.userId) {
id = user.userId; id = user.userId;
} }
if (id === userStore.currentUser.id) { if (id === currentUser?.id) {
const platform = userStore.currentUser.presence?.platform; const platform = currentUser.presence?.platform;
return { return {
...style, ...style,
...statusClass(user.status), ...statusClass(user.status),
@@ -89,10 +89,10 @@ function userStatusClass(user, pendingOffline = false) {
user.location === 'private' && user.location === 'private' &&
user.state === '' && user.state === '' &&
id && id &&
!userStore.currentUser.onlineFriends.includes(id) !(currentUser?.onlineFriends || []).includes(id)
) { ) {
// temp fix // temp fix
if (userStore.currentUser.activeFriends.includes(id)) { if ((currentUser?.activeFriends || []).includes(id)) {
// Active // Active
style.active = true; style.active = true;
} else { } else {
@@ -166,21 +166,22 @@ function statusClass(status) {
* @param {boolean} isIcon - is use for icon (about 40x40) * @param {boolean} isIcon - is use for icon (about 40x40)
* @param {string} resolution - requested icon resolution (default 128), * @param {string} resolution - requested icon resolution (default 128),
* @param {boolean} isUserDialogIcon - is use for user dialog icon * @param {boolean} isUserDialogIcon - is use for user dialog icon
* @param {boolean} displayVRCPlusIconsAsAvatar - from appearance settings store
* @returns {string} - img url * @returns {string} - img url
*/ */
function userImage( function userImage(
user, user,
isIcon = false, isIcon = false,
resolution = '128', resolution = '128',
isUserDialogIcon = false isUserDialogIcon = false,
displayVRCPlusIconsAsAvatar = false
) { ) {
const appAppearanceSettingsStore = useAppearanceSettingsStore();
if (!user) { if (!user) {
return ''; return '';
} }
if ( if (
(isUserDialogIcon && user.userIcon) || (isUserDialogIcon && user.userIcon) ||
(appAppearanceSettingsStore.displayVRCPlusIconsAsAvatar && (displayVRCPlusIconsAsAvatar &&
user.userIcon) user.userIcon)
) { ) {
if (isIcon) { if (isIcon) {
@@ -225,15 +226,15 @@ function userImage(
/** /**
* *
* @param {object} user * @param {object} user
* @param {boolean} displayVRCPlusIconsAsAvatar - from appearance settings store
* @returns {string|*} * @returns {string|*}
*/ */
function userImageFull(user) { function userImageFull(user, displayVRCPlusIconsAsAvatar = false) {
if (!user) { if (!user) {
return ''; return '';
} }
const appAppearanceSettingsStore = useAppearanceSettingsStore();
if ( if (
appAppearanceSettingsStore.displayVRCPlusIconsAsAvatar && displayVRCPlusIconsAsAvatar &&
user.userIcon user.userIcon
) { ) {
return user.userIcon; return user.userIcon;

View File

@@ -347,7 +347,11 @@ export const useNotificationStore = defineStore('Notification', () => {
} }
} }
} }
if (!checkCanInvite(currentLocation)) { if (!checkCanInvite(currentLocation, {
currentUserId: userStore.currentUser.id,
lastLocationStr: locationStore.lastLocation.location,
cachedInstances: instanceStore.cachedInstances
})) {
return; return;
} }

View File

@@ -405,7 +405,7 @@ export const useAppearanceSettingsStore = defineStore(
}); });
} }
if (randomUserColours.value) { if (randomUserColours.value) {
const colour = await getNameColour(userStore.currentUser.id); const colour = await getNameColour(userStore.currentUser.id, isDarkMode.value);
userStore.setCurrentUserColour(colour); userStore.setCurrentUserColour(colour);
userColourInit(); userColourInit();
} else { } else {
@@ -441,7 +441,7 @@ export const useAppearanceSettingsStore = defineStore(
for (const [userId, hue] of Object.entries(dictObject)) { for (const [userId, hue] of Object.entries(dictObject)) {
const ref = userStore.cachedUsers.get(userId); const ref = userStore.cachedUsers.get(userId);
if (typeof ref !== 'undefined') { if (typeof ref !== 'undefined') {
ref.$userColour = HueToHex(hue); ref.$userColour = HueToHex(hue, isDarkMode.value);
} }
} }
} }
@@ -460,7 +460,7 @@ export const useAppearanceSettingsStore = defineStore(
ref.$trustSortNum = trust.trustSortNum; ref.$trustSortNum = trust.trustSortNum;
if (randomUserColours.value && watchState.isFriendsLoaded) { if (randomUserColours.value && watchState.isFriendsLoaded) {
if (!ref.$userColour) { if (!ref.$userColour) {
getNameColour(ref.id).then((colour) => { getNameColour(ref.id, isDarkMode.value).then((colour) => {
ref.$userColour = colour; ref.$userColour = colour;
}); });
} }
@@ -1042,7 +1042,7 @@ export const useAppearanceSettingsStore = defineStore(
if (!randomUserColours.value) { if (!randomUserColours.value) {
return; return;
} }
const colour = await getNameColour(userStore.currentUser.id); const colour = await getNameColour(userStore.currentUser.id, isDarkMode.value);
userStore.setCurrentUserColour(colour); userStore.setCurrentUserColour(colour);
await userColourInit(); await userColourInit();
} }

View File

@@ -274,13 +274,14 @@
useModalStore, useModalStore,
useUserStore useUserStore
} from '../../../stores'; } from '../../../stores';
import { userImage, userStatusClass } from '../../../shared/utils'; import { useUserDisplay } from '../../../composables/useUserDisplay';
import { showUserDialog } from '../../../coordinators/userCoordinator'; import { showUserDialog } from '../../../coordinators/userCoordinator';
import { database } from '../../../services/database'; import { database } from '../../../services/database';
import { watchState } from '../../../services/watchState'; import { watchState } from '../../../services/watchState';
import configRepository from '../../../services/config'; import configRepository from '../../../services/config';
const { userImage, userStatusClass } = useUserDisplay();
const { t } = useI18n(); const { t } = useI18n();
const friendStore = useFriendStore(); const friendStore = useFriendStore();
const userStore = useUserStore(); const userStore = useUserStore();

View File

@@ -350,7 +350,8 @@
} from '../../components/ui/dropdown-menu'; } from '../../components/ui/dropdown-menu';
import { useAppearanceSettingsStore, useFavoriteStore, useModalStore, useUserStore } from '../../stores'; import { useAppearanceSettingsStore, useFavoriteStore, useModalStore, useUserStore } from '../../stores';
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../../components/ui/resizable'; import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../../components/ui/resizable';
import { debounce, userImage } from '../../shared/utils'; import { debounce } from '../../shared/utils';
import { useUserDisplay } from '../../composables/useUserDisplay';
import { favoriteRequest } from '../../api'; import { favoriteRequest } from '../../api';
import { useFavoritesCardScaling } from './composables/useFavoritesCardScaling.js'; import { useFavoritesCardScaling } from './composables/useFavoritesCardScaling.js';
import { useFavoritesGroupPanel } from './composables/useFavoritesGroupPanel.js'; import { useFavoritesGroupPanel } from './composables/useFavoritesGroupPanel.js';
@@ -370,6 +371,7 @@
import FavoritesToolbar from './components/FavoritesToolbar.vue'; import FavoritesToolbar from './components/FavoritesToolbar.vue';
import FriendExportDialog from './dialogs/FriendExportDialog.vue'; import FriendExportDialog from './dialogs/FriendExportDialog.vue';
const { userImage } = useUserDisplay();
const friendGroupVisibilityOptions = ref(['public', 'friends', 'private']); const friendGroupVisibilityOptions = ref(['public', 'friends', 'private']);
const { const {

View File

@@ -93,10 +93,11 @@
import { favoriteRequest } from '../../../api'; import { favoriteRequest } from '../../../api';
import { removeLocalFriendFavorite } from '../../../coordinators/favoriteCoordinator'; import { removeLocalFriendFavorite } from '../../../coordinators/favoriteCoordinator';
import { useFavoriteStore } from '../../../stores'; import { useFavoriteStore } from '../../../stores';
import { userImage } from '../../../shared/utils'; import { useUserDisplay } from '../../../composables/useUserDisplay';
import FavoritesMoveDropdown from './FavoritesMoveDropdown.vue'; import FavoritesMoveDropdown from './FavoritesMoveDropdown.vue';
const { userImage } = useUserDisplay();
const props = defineProps({ const props = defineProps({
favorite: { type: Object, required: true }, favorite: { type: Object, required: true },
group: { type: Object, default: null }, group: { type: Object, default: null },

View File

@@ -125,7 +125,8 @@
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { removeFromArray, userImage, userImageFull } from '../../../shared/utils'; import { removeFromArray } from '../../../shared/utils';
import { useUserDisplay } from '../../../composables/useUserDisplay';
import { useFavoriteStore, useGalleryStore, useUserStore } from '../../../stores'; import { useFavoriteStore, useGalleryStore, useUserStore } from '../../../stores';
import { addLocalFriendFavorite } from '../../../coordinators/favoriteCoordinator'; import { addLocalFriendFavorite } from '../../../coordinators/favoriteCoordinator';
import { favoriteRequest, userRequest } from '../../../api'; import { favoriteRequest, userRequest } from '../../../api';
@@ -133,6 +134,7 @@
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable'; import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
import { showUserDialog } from '../../../coordinators/userCoordinator'; import { showUserDialog } from '../../../coordinators/userCoordinator';
const { userImage, userImageFull } = useUserDisplay();
const { t } = useI18n(); const { t } = useI18n();
const emit = defineEmits(['update:friendImportDialogInput']); const emit = defineEmits(['update:friendImportDialogInput']);

View File

@@ -147,7 +147,7 @@
import { useVirtualizer } from '@tanstack/vue-virtual'; import { useVirtualizer } from '@tanstack/vue-virtual';
import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover';
import { useAppearanceSettingsStore, useFavoriteStore, useFriendStore } from '../../stores'; import { useAppearanceSettingsStore, useFavoriteStore, useFriendStore, useLocationStore } from '../../stores';
import { Slider } from '../../components/ui/slider'; import { Slider } from '../../components/ui/slider';
import { Switch } from '../../components/ui/switch'; import { Switch } from '../../components/ui/switch';
import { getFriendsLocations } from '../../shared/utils/location.js'; import { getFriendsLocations } from '../../shared/utils/location.js';
@@ -175,6 +175,9 @@
const favoriteStore = useFavoriteStore(); const favoriteStore = useFavoriteStore();
const { favoriteFriendGroups, groupedByGroupKeyFavoriteFriends, localFriendFavorites } = storeToRefs(favoriteStore); const { favoriteFriendGroups, groupedByGroupKeyFavoriteFriends, localFriendFavorites } = storeToRefs(favoriteStore);
const locationStore = useLocationStore();
const { lastLocation } = storeToRefs(locationStore);
const collapsedGroups = reactive(new Set()); const collapsedGroups = reactive(new Set());
const SEGMENTED_BASE_OPTIONS = [ const SEGMENTED_BASE_OPTIONS = [
@@ -319,7 +322,7 @@
.map((group, index) => { .map((group, index) => {
if (!Array.isArray(group) || group.length === 0) return null; if (!Array.isArray(group) || group.length === 0) return null;
const friends = group; const friends = group;
const instanceId = getFriendsLocations(friends) || `instance-${index + 1}`; const instanceId = getFriendsLocations(friends, lastLocation.value) || `instance-${index + 1}`;
return { return {
instanceId: String(instanceId), instanceId: String(instanceId),
friends friends

View File

@@ -75,13 +75,14 @@
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { isRealInstance, parseLocation, userImage, userStatusClass } from '../../../shared/utils'; import { isRealInstance, parseLocation } from '../../../shared/utils';
import { useGameStore, useLaunchStore, useLocationStore, useUserStore } from '../../../stores'; import { useGameStore, useLaunchStore, useLocationStore, useUserStore } from '../../../stores';
import { instanceRequest, notificationRequest, queryRequest } from '../../../api'; import { instanceRequest, notificationRequest, queryRequest } from '../../../api';
import { checkCanInvite, checkCanInviteSelf } from '../../../shared/utils/invite.js'; import { useInviteChecks } from '../../../composables/useInviteChecks';
import { useUserDisplay } from '../../../composables/useUserDisplay';
import Location from '../../../components/Location.vue'; import Location from '../../../components/Location.vue';
import { showUserDialog } from '../../../coordinators/userCoordinator'; import { showUserDialog } from '../../../coordinators/userCoordinator';
const { t } = useI18n(); const { t } = useI18n();
const { showSendBoopDialog } = useUserStore(); const { showSendBoopDialog } = useUserStore();
@@ -89,6 +90,8 @@ import { showUserDialog } from '../../../coordinators/userCoordinator';
const { lastLocation, lastLocationDestination } = storeToRefs(useLocationStore()); const { lastLocation, lastLocationDestination } = storeToRefs(useLocationStore());
const { isGameRunning } = storeToRefs(useGameStore()); const { isGameRunning } = storeToRefs(useGameStore());
const { currentUser } = storeToRefs(useUserStore()); const { currentUser } = storeToRefs(useUserStore());
const { checkCanInvite, checkCanInviteSelf } = useInviteChecks();
const { userImage, userStatusClass } = useUserDisplay();
const props = defineProps({ const props = defineProps({
friend: { friend: {

View File

@@ -179,11 +179,13 @@
import { useAppearanceSettingsStore, useAuthStore, useVRCXUpdaterStore } from '../../stores'; import { useAppearanceSettingsStore, useAuthStore, useVRCXUpdaterStore } from '../../stores';
import { getLanguageName, languageCodes } from '../../localization'; import { getLanguageName, languageCodes } from '../../localization';
import { openExternalLink, userImage } from '../../shared/utils'; import { openExternalLink } from '../../shared/utils';
import { useUserDisplay } from '../../composables/useUserDisplay';
import { watchState } from '../../services/watchState'; import { watchState } from '../../services/watchState';
import LoginSettingsDialog from './Dialog/LoginSettingsDialog.vue'; import LoginSettingsDialog from './Dialog/LoginSettingsDialog.vue';
const { userImage } = useUserDisplay();
const { showVRCXUpdateDialog } = useVRCXUpdaterStore(); const { showVRCXUpdateDialog } = useVRCXUpdaterStore();
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();

View File

@@ -27,10 +27,12 @@ import {
} from 'lucide-vue-next'; } from 'lucide-vue-next';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { checkCanInvite, formatDateFilter } from '../../shared/utils'; import { formatDateFilter } from '../../shared/utils';
import { checkCanInvite } from '../../shared/utils/invite';
import { i18n } from '../../plugins'; import { i18n } from '../../plugins';
import { import {
useGameStore, useGameStore,
useInstanceStore,
useLocationStore, useLocationStore,
useUiStore, useUiStore,
useUserStore, useUserStore,
@@ -70,10 +72,16 @@ export const createColumns = ({
const { isGameRunning } = storeToRefs(useGameStore()); const { isGameRunning } = storeToRefs(useGameStore());
const { isNotificationExpired } = useNotificationStore(); const { isNotificationExpired } = useNotificationStore();
const { cachedInstances } = storeToRefs(useInstanceStore());
const canInvite = () => { const canInvite = () => {
const location = lastLocation.value?.location; const location = lastLocation.value?.location;
return ( return (
Boolean(location) && isGameRunning.value && checkCanInvite(location) Boolean(location) && isGameRunning.value && checkCanInvite(location, {
currentUserId: currentUser.value?.id,
lastLocationStr: lastLocation.value?.location,
cachedInstances: cachedInstances.value
})
); );
}; };

View File

@@ -77,7 +77,7 @@
import Timer from '@/components/Timer.vue'; import Timer from '@/components/Timer.vue';
import { useAppearanceSettingsStore, useFriendStore, useUserStore } from '../../../stores'; import { useAppearanceSettingsStore, useFriendStore, useUserStore } from '../../../stores';
import { userImage, userStatusClass } from '../../../shared/utils'; import { useUserDisplay } from '../../../composables/useUserDisplay';
import '@/styles/status-icon.css'; import '@/styles/status-icon.css';
import { showUserDialog } from '../../../coordinators/userCoordinator'; import { showUserDialog } from '../../../coordinators/userCoordinator';
@@ -90,6 +90,7 @@ import { confirmDeleteFriend } from '../../../coordinators/friendRelationshipCoo
const { hideNicknames } = storeToRefs(useAppearanceSettingsStore()); const { hideNicknames } = storeToRefs(useAppearanceSettingsStore());
const { isRefreshFriendsLoading, allFavoriteFriendIds } = storeToRefs(useFriendStore()); const { isRefreshFriendsLoading, allFavoriteFriendIds } = storeToRefs(useFriendStore());
const { userImage, userStatusClass } = useUserDisplay();
const { t } = useI18n(); const { t } = useI18n();

View File

@@ -204,9 +204,10 @@
useUserStore useUserStore
} from '../../../stores'; } from '../../../stores';
import { buildFriendRow, buildInstanceHeaderRow, buildToggleRow, estimateRowSize } from '../friendsSidebarUtils'; import { buildFriendRow, buildInstanceHeaderRow, buildToggleRow, estimateRowSize } from '../friendsSidebarUtils';
import { getFriendsSortFunction, isRealInstance, userImage, userStatusClass } from '../../../shared/utils'; import { getFriendsSortFunction, isRealInstance } from '../../../shared/utils';
import { instanceRequest, notificationRequest, queryRequest, userRequest } from '../../../api'; import { instanceRequest, notificationRequest, queryRequest, userRequest } from '../../../api';
import { checkCanInvite, checkCanInviteSelf } from '../../../shared/utils/invite.js'; import { useInviteChecks } from '../../../composables/useInviteChecks';
import { useUserDisplay } from '../../../composables/useUserDisplay';
import { getFriendsLocations } from '../../../shared/utils/location.js'; import { getFriendsLocations } from '../../../shared/utils/location.js';
import { parseLocation } from '../../../shared/utils'; import { parseLocation } from '../../../shared/utils';
@@ -246,6 +247,8 @@ import { showUserDialog } from '../../../coordinators/userCoordinator';
const { lastLocation, lastLocationDestination } = storeToRefs(useLocationStore()); const { lastLocation, lastLocationDestination } = storeToRefs(useLocationStore());
const { isGameRunning } = storeToRefs(useGameStore()); const { isGameRunning } = storeToRefs(useGameStore());
const { currentUser } = storeToRefs(useUserStore()); const { currentUser } = storeToRefs(useUserStore());
const { checkCanInvite, checkCanInviteSelf } = useInviteChecks();
const { userImage, userStatusClass } = useUserDisplay();
const isFriendsGroupMe = ref(true); const isFriendsGroupMe = ref(true);
const isVIPFriends = ref(true); const isVIPFriends = ref(true);
@@ -476,7 +479,7 @@ import { showUserDialog } from '../../../coordinators/userCoordinator';
if (!friendArr || !friendArr.length) return; if (!friendArr || !friendArr.length) return;
const groupKey = friendArr?.[0]?.ref?.$location?.tag ?? `group-${groupIndex}`; const groupKey = friendArr?.[0]?.ref?.$location?.tag ?? `group-${groupIndex}`;
rows.push( rows.push(
buildInstanceHeaderRow(getFriendsLocations(friendArr), friendArr.length, `instance:${groupKey}`) buildInstanceHeaderRow(getFriendsLocations(friendArr, lastLocation.value), friendArr.length, `instance:${groupKey}`)
); );
friendArr.forEach((friend, idx) => { friendArr.forEach((friend, idx) => {
rows.push( rows.push(

View File

@@ -95,7 +95,8 @@
ContextMenuTrigger ContextMenuTrigger
} from '../../../components/ui/context-menu'; } from '../../../components/ui/context-menu';
import { buildGroupHeaderRow, buildGroupItemRow, estimateGroupRowSize, getGroupId } from '../groupsSidebarUtils'; import { buildGroupHeaderRow, buildGroupItemRow, estimateGroupRowSize, getGroupId } from '../groupsSidebarUtils';
import { checkCanInviteSelf, convertFileUrlToImageUrl, parseLocation } from '../../../shared/utils'; import { convertFileUrlToImageUrl, parseLocation } from '../../../shared/utils';
import { useInviteChecks } from '../../../composables/useInviteChecks';
import { useAppearanceSettingsStore, useGroupStore, useLaunchStore } from '../../../stores'; import { useAppearanceSettingsStore, useGroupStore, useLaunchStore } from '../../../stores';
import { instanceRequest } from '../../../api'; import { instanceRequest } from '../../../api';
@@ -108,6 +109,7 @@
const { isAgeGatedInstancesVisible } = storeToRefs(useAppearanceSettingsStore()); const { isAgeGatedInstancesVisible } = storeToRefs(useAppearanceSettingsStore());
const { showGroupDialog, sortGroupInstancesByInGame } = useGroupStore(); const { showGroupDialog, sortGroupInstancesByInGame } = useGroupStore();
const { groupInstances } = storeToRefs(useGroupStore()); const { groupInstances } = storeToRefs(useGroupStore());
const { checkCanInviteSelf } = useInviteChecks();
const groupInstancesCfg = ref({}); const groupInstancesCfg = ref({});
const scrollViewportRef = ref(null); const scrollViewportRef = ref(null);

View File

@@ -257,7 +257,8 @@
import { useGameStore, useGroupStore, useLocationStore, useNotificationStore, useUserStore } from '../../../stores'; import { useGameStore, useGroupStore, useLocationStore, useNotificationStore, useUserStore } from '../../../stores';
import { showGroupDialog } from '../../../coordinators/groupCoordinator'; import { showGroupDialog } from '../../../coordinators/groupCoordinator';
import { showUserDialog } from '../../../coordinators/userCoordinator'; import { showUserDialog } from '../../../coordinators/userCoordinator';
import { checkCanInvite, userImage } from '../../../shared/utils'; import { useInviteChecks } from '../../../composables/useInviteChecks';
import { useUserDisplay } from '../../../composables/useUserDisplay';
import Location from '../../../components/Location.vue'; import Location from '../../../components/Location.vue';
@@ -275,6 +276,8 @@
const { lastLocation } = storeToRefs(useLocationStore()); const { lastLocation } = storeToRefs(useLocationStore());
const { isGameRunning } = storeToRefs(useGameStore()); const { isGameRunning } = storeToRefs(useGameStore());
const { openNotificationLink, isNotificationExpired } = useNotificationStore(); const { openNotificationLink, isNotificationExpired } = useNotificationStore();
const { checkCanInvite } = useInviteChecks();
const { userImage } = useUserDisplay();
const senderName = computed(() => { const senderName = computed(() => {
const n = props.notification; const n = props.notification;

View File

@@ -59,7 +59,8 @@
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { removeFromArray, userImage, userImageFull } from '../../../shared/utils'; import { removeFromArray } from '../../../shared/utils';
import { useUserDisplay } from '../../../composables/useUserDisplay';
import { useFriendStore, useGalleryStore, useUserStore } from '../../../stores'; import { useFriendStore, useGalleryStore, useUserStore } from '../../../stores';
import { createColumns } from './noteExportColumns.jsx'; import { createColumns } from './noteExportColumns.jsx';
import { miscRequest } from '../../../api'; import { miscRequest } from '../../../api';
@@ -68,6 +69,7 @@
import * as workerTimers from 'worker-timers'; import * as workerTimers from 'worker-timers';
import { showUserDialog } from '../../../coordinators/userCoordinator'; import { showUserDialog } from '../../../coordinators/userCoordinator';
const { userImage, userImageFull } = useUserDisplay();
const { t } = useI18n(); const { t } = useI18n();
const { friends } = storeToRefs(useFriendStore()); const { friends } = storeToRefs(useFriendStore());