mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-21 15:53:50 +02:00
refactor
This commit is contained in:
@@ -602,50 +602,26 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
AlertTriangle,
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
Copy,
|
||||
Download,
|
||||
DownloadIcon,
|
||||
Eye,
|
||||
Info,
|
||||
Languages,
|
||||
LogOut,
|
||||
MoreHorizontal,
|
||||
Pencil,
|
||||
RefreshCw,
|
||||
Tag,
|
||||
Trash2
|
||||
} from 'lucide-vue-next';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { computed, defineAsyncComponent, nextTick, ref, watch } from 'vue';
|
||||
import { Copy, Info, Languages, MoreHorizontal, Pencil, Trash2 } from 'lucide-vue-next';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { computed, defineAsyncComponent, ref, watch } from 'vue';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { DataTableEmpty } from '@/components/ui/data-table';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { TabsUnderline } from '@/components/ui/tabs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import DeprecationAlert from '@/components/DeprecationAlert.vue';
|
||||
import VueJsonPretty from 'vue-json-pretty';
|
||||
|
||||
import {
|
||||
useAdvancedSettingsStore,
|
||||
useAppearanceSettingsStore,
|
||||
useAuthStore,
|
||||
useAvatarStore,
|
||||
useFavoriteStore,
|
||||
useFriendStore,
|
||||
@@ -657,19 +633,16 @@
|
||||
useModalStore,
|
||||
useModerationStore,
|
||||
useNotificationStore,
|
||||
useUiStore,
|
||||
useUserStore,
|
||||
useWorldStore
|
||||
} from '../../../stores';
|
||||
import {
|
||||
copyToClipboard,
|
||||
downloadAndSaveJson,
|
||||
formatDateFilter,
|
||||
getFaviconUrl,
|
||||
isFriendOnline,
|
||||
isRealInstance,
|
||||
openExternalLink,
|
||||
parseLocation,
|
||||
refreshInstancePlayerCount,
|
||||
timeToText,
|
||||
userImage,
|
||||
@@ -677,18 +650,9 @@
|
||||
userOnlineForTimestamp,
|
||||
userStatusClass
|
||||
} from '../../../shared/utils';
|
||||
import {
|
||||
favoriteRequest,
|
||||
friendRequest,
|
||||
miscRequest,
|
||||
notificationRequest,
|
||||
playerModerationRequest,
|
||||
userRequest,
|
||||
worldRequest
|
||||
} from '../../../api';
|
||||
import { database } from '../../../service/database';
|
||||
import { miscRequest, userRequest } from '../../../api';
|
||||
import { formatJsonVars } from '../../../shared/utils/base/ui';
|
||||
import { processBulk } from '../../../service/request';
|
||||
import { useUserDialogCommands } from './useUserDialogCommands';
|
||||
|
||||
import DialogJsonTab from '../DialogJsonTab.vue';
|
||||
import InstanceActionBar from '../../InstanceActionBar.vue';
|
||||
@@ -757,6 +721,39 @@
|
||||
|
||||
const { applyPlayerModeration, handlePlayerModerationDelete } = useModerationStore();
|
||||
|
||||
const {
|
||||
sendInviteDialogVisible,
|
||||
sendInviteDialog,
|
||||
sendInviteRequestDialogVisible,
|
||||
userDialogCommand,
|
||||
registerCallbacks
|
||||
} = useUserDialogCommands(userDialog, {
|
||||
t,
|
||||
toast,
|
||||
modalStore,
|
||||
currentUser,
|
||||
cachedUsers,
|
||||
friendLogTable,
|
||||
lastLocation,
|
||||
lastLocationDestination,
|
||||
inviteGroupDialog,
|
||||
showUserDialog,
|
||||
showFavoriteDialog,
|
||||
showAvatarDialog,
|
||||
showAvatarAuthorDialog,
|
||||
showModerateGroupDialog,
|
||||
showSendBoopDialog,
|
||||
showGalleryPage,
|
||||
getFriendRequest,
|
||||
handleFriendDelete,
|
||||
applyPlayerModeration,
|
||||
handlePlayerModerationDelete,
|
||||
refreshInviteMessageTableData,
|
||||
clearInviteImageUpload,
|
||||
instanceStore,
|
||||
useNotificationStore
|
||||
});
|
||||
|
||||
watch(
|
||||
() => userDialog.value.loading,
|
||||
() => {
|
||||
@@ -778,14 +775,6 @@
|
||||
const userDialogLastWorld = ref('');
|
||||
const userDialogLastFavoriteWorld = ref('');
|
||||
|
||||
const sendInviteDialogVisible = ref(false);
|
||||
const sendInviteDialog = ref({
|
||||
messageSlot: {},
|
||||
userId: '',
|
||||
params: {}
|
||||
});
|
||||
const sendInviteRequestDialogVisible = ref(false);
|
||||
|
||||
const socialStatusDialog = ref({
|
||||
visible: false,
|
||||
loading: false,
|
||||
@@ -966,6 +955,17 @@
|
||||
D.visible = true;
|
||||
}
|
||||
|
||||
// Register simple dialog openers as callbacks for the command composable
|
||||
registerCallbacks({
|
||||
showSocialStatusDialog,
|
||||
showLanguageDialog,
|
||||
showBioDialog,
|
||||
showPronounsDialog,
|
||||
showEditNoteAndMemoDialog: () => {
|
||||
isEditNoteAndMemoDialogVisible.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -1028,450 +1028,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param userId
|
||||
* @param type
|
||||
*/
|
||||
function setPlayerModeration(userId, type) {
|
||||
const D = userDialog.value;
|
||||
AppApi.SetVRChatUserModeration(currentUser.value.id, userId, type).then((result) => {
|
||||
if (result) {
|
||||
if (type === 4) {
|
||||
D.isShowAvatar = false;
|
||||
D.isHideAvatar = true;
|
||||
} else if (type === 5) {
|
||||
D.isShowAvatar = true;
|
||||
D.isHideAvatar = false;
|
||||
} else {
|
||||
D.isShowAvatar = false;
|
||||
D.isHideAvatar = false;
|
||||
}
|
||||
} else {
|
||||
toast.error(t('message.avatar.change_moderation_failed'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param params
|
||||
* @param userId
|
||||
*/
|
||||
function showSendInviteDialog(params, userId) {
|
||||
sendInviteDialog.value = {
|
||||
params,
|
||||
userId,
|
||||
messageSlot: {}
|
||||
};
|
||||
refreshInviteMessageTableData('message');
|
||||
clearInviteImageUpload();
|
||||
sendInviteDialogVisible.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param params
|
||||
* @param userId
|
||||
*/
|
||||
function showSendInviteRequestDialog(params, userId) {
|
||||
sendInviteDialog.value = {
|
||||
params,
|
||||
userId,
|
||||
messageSlot: {}
|
||||
};
|
||||
refreshInviteMessageTableData('request');
|
||||
clearInviteImageUpload();
|
||||
sendInviteRequestDialogVisible.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param groupId
|
||||
* @param userId
|
||||
*/
|
||||
function showInviteGroupDialog(groupId, userId) {
|
||||
inviteGroupDialog.value.groupId = groupId;
|
||||
inviteGroupDialog.value.userId = userId;
|
||||
inviteGroupDialog.value.visible = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param command
|
||||
*/
|
||||
function userDialogCommand(command) {
|
||||
let L;
|
||||
const D = userDialog.value;
|
||||
if (D.visible === false) {
|
||||
return;
|
||||
}
|
||||
if (command === 'Refresh') {
|
||||
const userId = D.id;
|
||||
D.id = '';
|
||||
showUserDialog(userId);
|
||||
} else if (command === 'Share') {
|
||||
copyUserURL(D.id);
|
||||
} else if (command === 'Add Favorite') {
|
||||
showFavoriteDialog('friend', D.id);
|
||||
} else if (command === 'Edit Social Status') {
|
||||
showSocialStatusDialog();
|
||||
} else if (command === 'Edit Language') {
|
||||
showLanguageDialog();
|
||||
} else if (command === 'Edit Bio') {
|
||||
showBioDialog();
|
||||
} else if (command === 'Edit Pronouns') {
|
||||
showPronounsDialog();
|
||||
} else if (command === 'Request Invite') {
|
||||
notificationRequest
|
||||
.sendRequestInvite(
|
||||
{
|
||||
platform: 'standalonewindows'
|
||||
},
|
||||
D.id
|
||||
)
|
||||
.then((args) => {
|
||||
toast('Request invite sent');
|
||||
return args;
|
||||
});
|
||||
} else if (command === 'Invite Message') {
|
||||
L = parseLocation(lastLocation.value.location);
|
||||
worldRequest
|
||||
.getCachedWorld({
|
||||
worldId: L.worldId
|
||||
})
|
||||
.then((args) => {
|
||||
showSendInviteDialog(
|
||||
{
|
||||
instanceId: lastLocation.value.location,
|
||||
worldId: lastLocation.value.location,
|
||||
worldName: args.ref.name
|
||||
},
|
||||
D.id
|
||||
);
|
||||
});
|
||||
} else if (command === 'Request Invite Message') {
|
||||
showSendInviteRequestDialog(
|
||||
{
|
||||
platform: 'standalonewindows'
|
||||
},
|
||||
D.id
|
||||
);
|
||||
} else if (command === 'Invite') {
|
||||
let currentLocation = lastLocation.value.location;
|
||||
if (lastLocation.value.location === 'traveling') {
|
||||
currentLocation = lastLocationDestination.value;
|
||||
}
|
||||
L = parseLocation(currentLocation);
|
||||
worldRequest
|
||||
.getCachedWorld({
|
||||
worldId: L.worldId
|
||||
})
|
||||
.then((args) => {
|
||||
notificationRequest
|
||||
.sendInvite(
|
||||
{
|
||||
instanceId: L.tag,
|
||||
worldId: L.tag,
|
||||
worldName: args.ref.name
|
||||
},
|
||||
D.id
|
||||
)
|
||||
.then((_args) => {
|
||||
toast(t('message.invite.sent'));
|
||||
return _args;
|
||||
});
|
||||
});
|
||||
} else if (command === 'Show Avatar Author') {
|
||||
const { currentAvatarImageUrl } = D.ref;
|
||||
showAvatarAuthorDialog(D.id, D.$avatarInfo.ownerId, currentAvatarImageUrl);
|
||||
} else if (command === 'Show Fallback Avatar Details') {
|
||||
const { fallbackAvatar } = D.ref;
|
||||
if (fallbackAvatar) {
|
||||
showAvatarDialog(fallbackAvatar);
|
||||
} else {
|
||||
toast.error('No fallback avatar set');
|
||||
}
|
||||
} else if (command === 'Previous Instances') {
|
||||
showPreviousInstancesListDialog(D.ref);
|
||||
} else if (command === 'Manage Gallery') {
|
||||
userDialog.value.visible = false;
|
||||
showGalleryPage();
|
||||
} else if (command === 'Invite To Group') {
|
||||
showInviteGroupDialog('', D.id);
|
||||
} else if (command === 'Send Boop') {
|
||||
showSendBoopDialog(D.id);
|
||||
} else if (command === 'Group Moderation') {
|
||||
showModerateGroupDialog(D.id);
|
||||
} else if (command === 'Hide Avatar') {
|
||||
if (D.isHideAvatar) {
|
||||
setPlayerModeration(D.id, 0);
|
||||
} else {
|
||||
setPlayerModeration(D.id, 4);
|
||||
}
|
||||
} else if (command === 'Show Avatar') {
|
||||
if (D.isShowAvatar) {
|
||||
setPlayerModeration(D.id, 0);
|
||||
} else {
|
||||
setPlayerModeration(D.id, 5);
|
||||
}
|
||||
} else if (command === 'Edit Note Memo') {
|
||||
isEditNoteAndMemoDialogVisible.value = true;
|
||||
} else {
|
||||
const i18nPreFix = 'dialog.user.actions.';
|
||||
const formattedCommand = command.toLowerCase().replace(/ /g, '_');
|
||||
const displayCommandText = t(`${i18nPreFix}${formattedCommand}`).includes('i18nPreFix')
|
||||
? command
|
||||
: t(`${i18nPreFix}${formattedCommand}`);
|
||||
|
||||
modalStore
|
||||
.confirm({
|
||||
description: t('confirm.message', {
|
||||
command: displayCommandText
|
||||
}),
|
||||
title: t('confirm.title'),
|
||||
confirmText: t('confirm.confirm_button'),
|
||||
cancelText: t('confirm.cancel_button')
|
||||
})
|
||||
.then(({ ok }) => {
|
||||
if (ok) {
|
||||
performUserDialogCommand(command, D.id);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param args
|
||||
*/
|
||||
function handleSendFriendRequest(args) {
|
||||
const ref = cachedUsers.get(args.params.userId);
|
||||
if (typeof ref === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const friendLogHistory = {
|
||||
created_at: new Date().toJSON(),
|
||||
type: 'FriendRequest',
|
||||
userId: ref.id,
|
||||
displayName: ref.displayName
|
||||
};
|
||||
friendLogTable.value.data.push(friendLogHistory);
|
||||
database.addFriendLogHistory(friendLogHistory);
|
||||
|
||||
const D = userDialog.value;
|
||||
if (D.visible === false || D.id !== args.params.userId) {
|
||||
return;
|
||||
}
|
||||
if (args.json.success) {
|
||||
D.isFriend = true;
|
||||
} else {
|
||||
D.outgoingRequest = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param args
|
||||
*/
|
||||
function handleCancelFriendRequest(args) {
|
||||
const ref = cachedUsers.get(args.params.userId);
|
||||
if (typeof ref === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const friendLogHistory = {
|
||||
created_at: new Date().toJSON(),
|
||||
type: 'CancelFriendRequest',
|
||||
userId: ref.id,
|
||||
displayName: ref.displayName
|
||||
};
|
||||
friendLogTable.value.data.push(friendLogHistory);
|
||||
database.addFriendLogHistory(friendLogHistory);
|
||||
const D = userDialog.value;
|
||||
if (D.visible === false || D.id !== args.params.userId) {
|
||||
return;
|
||||
}
|
||||
D.outgoingRequest = false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param args
|
||||
*/
|
||||
function handleSendPlayerModeration(args) {
|
||||
const ref = applyPlayerModeration(args.json);
|
||||
const D = userDialog.value;
|
||||
if (D.visible === false || (ref.targetUserId !== D.id && ref.sourceUserId !== currentUser.value.id)) {
|
||||
return;
|
||||
}
|
||||
if (ref.type === 'block') {
|
||||
D.isBlock = true;
|
||||
} else if (ref.type === 'mute') {
|
||||
D.isMute = true;
|
||||
} else if (ref.type === 'interactOff') {
|
||||
D.isInteractOff = true;
|
||||
} else if (ref.type === 'muteChat') {
|
||||
D.isMuteChat = true;
|
||||
}
|
||||
toast.success(t('message.user.moderated'));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param command
|
||||
* @param userId
|
||||
*/
|
||||
async function performUserDialogCommand(command, userId) {
|
||||
let args;
|
||||
let key;
|
||||
switch (command) {
|
||||
case 'Delete Favorite':
|
||||
favoriteRequest.deleteFavorite({
|
||||
objectId: userId
|
||||
});
|
||||
break;
|
||||
case 'Accept Friend Request':
|
||||
key = getFriendRequest(userId);
|
||||
if (key === '') {
|
||||
const args = await friendRequest.sendFriendRequest({
|
||||
userId
|
||||
});
|
||||
handleSendFriendRequest(args);
|
||||
} else {
|
||||
notificationRequest
|
||||
.acceptFriendRequestNotification({
|
||||
notificationId: key
|
||||
})
|
||||
.then((args) => {
|
||||
useNotificationStore().handleNotificationAccept(args);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err && err.message && err.message.includes('404')) {
|
||||
useNotificationStore().handleNotificationHide(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'Decline Friend Request':
|
||||
key = getFriendRequest(userId);
|
||||
if (key === '') {
|
||||
const args = await friendRequest.cancelFriendRequest({
|
||||
userId
|
||||
});
|
||||
handleCancelFriendRequest(args);
|
||||
} else {
|
||||
notificationRequest
|
||||
.hideNotification({
|
||||
notificationId: key
|
||||
})
|
||||
.then(() => {
|
||||
useNotificationStore().handleNotificationHide(key);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'Cancel Friend Request': {
|
||||
args = await friendRequest.cancelFriendRequest({
|
||||
userId
|
||||
});
|
||||
handleCancelFriendRequest(args);
|
||||
break;
|
||||
}
|
||||
case 'Send Friend Request': {
|
||||
args = await friendRequest.sendFriendRequest({
|
||||
userId
|
||||
});
|
||||
handleSendFriendRequest(args);
|
||||
break;
|
||||
}
|
||||
case 'Moderation Unblock':
|
||||
args = await playerModerationRequest.deletePlayerModeration({
|
||||
moderated: userId,
|
||||
type: 'block'
|
||||
});
|
||||
handlePlayerModerationDelete(args);
|
||||
break;
|
||||
case 'Moderation Block': {
|
||||
args = await playerModerationRequest.sendPlayerModeration({
|
||||
moderated: userId,
|
||||
type: 'block'
|
||||
});
|
||||
handleSendPlayerModeration(args);
|
||||
break;
|
||||
}
|
||||
case 'Moderation Unmute':
|
||||
args = await playerModerationRequest.deletePlayerModeration({
|
||||
moderated: userId,
|
||||
type: 'mute'
|
||||
});
|
||||
handlePlayerModerationDelete(args);
|
||||
break;
|
||||
case 'Moderation Mute': {
|
||||
args = await playerModerationRequest.sendPlayerModeration({
|
||||
moderated: userId,
|
||||
type: 'mute'
|
||||
});
|
||||
handleSendPlayerModeration(args);
|
||||
break;
|
||||
}
|
||||
case 'Moderation Enable Avatar Interaction':
|
||||
args = await playerModerationRequest.deletePlayerModeration({
|
||||
moderated: userId,
|
||||
type: 'interactOff'
|
||||
});
|
||||
handlePlayerModerationDelete(args);
|
||||
break;
|
||||
case 'Moderation Disable Avatar Interaction': {
|
||||
args = await playerModerationRequest.sendPlayerModeration({
|
||||
moderated: userId,
|
||||
type: 'interactOff'
|
||||
});
|
||||
handleSendPlayerModeration(args);
|
||||
break;
|
||||
}
|
||||
case 'Moderation Enable Chatbox':
|
||||
args = await playerModerationRequest.deletePlayerModeration({
|
||||
moderated: userId,
|
||||
type: 'muteChat'
|
||||
});
|
||||
handlePlayerModerationDelete(args);
|
||||
break;
|
||||
case 'Moderation Disable Chatbox': {
|
||||
args = await playerModerationRequest.sendPlayerModeration({
|
||||
moderated: userId,
|
||||
type: 'muteChat'
|
||||
});
|
||||
handleSendPlayerModeration(args);
|
||||
break;
|
||||
}
|
||||
case 'Report Hacking':
|
||||
reportUserForHacking(userId);
|
||||
break;
|
||||
case 'Unfriend':
|
||||
args = await friendRequest.deleteFriend(
|
||||
{
|
||||
userId
|
||||
},
|
||||
t('dialog.user.actions.unfriend_success_msg')
|
||||
);
|
||||
handleFriendDelete(args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param userId
|
||||
*/
|
||||
function reportUserForHacking(userId) {
|
||||
miscRequest.reportUser({
|
||||
userId,
|
||||
contentType: 'user',
|
||||
reason: 'behavior-hacking',
|
||||
type: 'report'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,532 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { ref } from 'vue';
|
||||
import { useUserDialogCommands } from '../useUserDialogCommands';
|
||||
|
||||
// Mock external modules
|
||||
vi.mock('../../../../api', () => ({
|
||||
favoriteRequest: {
|
||||
deleteFavorite: vi.fn()
|
||||
},
|
||||
friendRequest: {
|
||||
sendFriendRequest: vi.fn(),
|
||||
cancelFriendRequest: vi.fn(),
|
||||
deleteFriend: vi.fn()
|
||||
},
|
||||
miscRequest: {
|
||||
reportUser: vi.fn()
|
||||
},
|
||||
notificationRequest: {
|
||||
sendRequestInvite: vi.fn(() => Promise.resolve({})),
|
||||
sendInvite: vi.fn(() => Promise.resolve({})),
|
||||
acceptFriendRequestNotification: vi.fn(() => Promise.resolve({})),
|
||||
hideNotification: vi.fn(() => Promise.resolve({}))
|
||||
},
|
||||
playerModerationRequest: {
|
||||
sendPlayerModeration: vi.fn(),
|
||||
deletePlayerModeration: vi.fn()
|
||||
},
|
||||
worldRequest: {
|
||||
getCachedWorld: vi.fn(() =>
|
||||
Promise.resolve({ ref: { name: 'TestWorld' } })
|
||||
)
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('../../../../shared/utils', () => ({
|
||||
copyToClipboard: vi.fn(),
|
||||
parseLocation: vi.fn(() => ({ worldId: 'wrld_test', tag: 'wrld_test~123' }))
|
||||
}));
|
||||
|
||||
vi.mock('../../../../service/database', () => ({
|
||||
database: {
|
||||
addFriendLogHistory: vi.fn()
|
||||
}
|
||||
}));
|
||||
|
||||
// Import mocks after vi.mock
|
||||
const { copyToClipboard } = await import('../../../../shared/utils');
|
||||
const {
|
||||
favoriteRequest,
|
||||
friendRequest,
|
||||
notificationRequest,
|
||||
playerModerationRequest,
|
||||
miscRequest
|
||||
} = await import('../../../../api');
|
||||
const { database } = await import('../../../../service/database');
|
||||
|
||||
function createMockUserDialog() {
|
||||
return ref({
|
||||
visible: true,
|
||||
id: 'usr_test123',
|
||||
ref: {
|
||||
displayName: 'TestUser',
|
||||
currentAvatarImageUrl: 'https://example.com/avatar.png',
|
||||
fallbackAvatar: 'avtr_fallback',
|
||||
location: 'wrld_test~123'
|
||||
},
|
||||
$avatarInfo: { ownerId: 'usr_owner' },
|
||||
isFriend: true,
|
||||
isBlock: false,
|
||||
isMute: false,
|
||||
isInteractOff: false,
|
||||
isMuteChat: false,
|
||||
isShowAvatar: false,
|
||||
isHideAvatar: false,
|
||||
outgoingRequest: false,
|
||||
incomingRequest: false
|
||||
});
|
||||
}
|
||||
|
||||
function createMockDeps(overrides = {}) {
|
||||
return {
|
||||
t: vi.fn((key) => key),
|
||||
toast: Object.assign(vi.fn(), {
|
||||
success: vi.fn(),
|
||||
error: vi.fn()
|
||||
}),
|
||||
modalStore: {
|
||||
confirm: vi.fn(() => Promise.resolve({ ok: true }))
|
||||
},
|
||||
currentUser: ref({ id: 'usr_current', isBoopingEnabled: true }),
|
||||
cachedUsers: new Map([
|
||||
['usr_test123', { id: 'usr_test123', displayName: 'TestUser' }]
|
||||
]),
|
||||
friendLogTable: ref({ data: [] }),
|
||||
lastLocation: ref({ location: 'wrld_test~123' }),
|
||||
lastLocationDestination: ref('wrld_dest~456'),
|
||||
inviteGroupDialog: ref({ groupId: '', userId: '', visible: false }),
|
||||
showUserDialog: vi.fn(),
|
||||
showFavoriteDialog: vi.fn(),
|
||||
showAvatarDialog: vi.fn(),
|
||||
showAvatarAuthorDialog: vi.fn(),
|
||||
showModerateGroupDialog: vi.fn(),
|
||||
showSendBoopDialog: vi.fn(),
|
||||
showGalleryPage: vi.fn(),
|
||||
getFriendRequest: vi.fn(() => ''),
|
||||
handleFriendDelete: vi.fn(),
|
||||
applyPlayerModeration: vi.fn((json) => json),
|
||||
handlePlayerModerationDelete: vi.fn(),
|
||||
refreshInviteMessageTableData: vi.fn(),
|
||||
clearInviteImageUpload: vi.fn(),
|
||||
instanceStore: {
|
||||
showPreviousInstancesListDialog: vi.fn()
|
||||
},
|
||||
useNotificationStore: vi.fn(() => ({
|
||||
handleNotificationAccept: vi.fn(),
|
||||
handleNotificationHide: vi.fn()
|
||||
})),
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
|
||||
describe('useUserDialogCommands', () => {
|
||||
let userDialog;
|
||||
let deps;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
userDialog = createMockUserDialog();
|
||||
deps = createMockDeps();
|
||||
});
|
||||
|
||||
describe('userDialogCommand — direct commands', () => {
|
||||
it('should not execute when dialog is not visible', () => {
|
||||
userDialog.value.visible = false;
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Refresh');
|
||||
expect(deps.showUserDialog).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Refresh: should reset id and reopen dialog', () => {
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Refresh');
|
||||
expect(userDialog.value.id).toBe('');
|
||||
expect(deps.showUserDialog).toHaveBeenCalledWith('usr_test123');
|
||||
});
|
||||
|
||||
it('Share: should copy user URL', () => {
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Share');
|
||||
expect(copyToClipboard).toHaveBeenCalledWith(
|
||||
'https://vrchat.com/home/user/usr_test123',
|
||||
'User URL copied to clipboard'
|
||||
);
|
||||
});
|
||||
|
||||
it('Add Favorite: should call showFavoriteDialog', () => {
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Add Favorite');
|
||||
expect(deps.showFavoriteDialog).toHaveBeenCalledWith(
|
||||
'friend',
|
||||
'usr_test123'
|
||||
);
|
||||
});
|
||||
|
||||
it('Show Avatar Author: should call showAvatarAuthorDialog', () => {
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Show Avatar Author');
|
||||
expect(deps.showAvatarAuthorDialog).toHaveBeenCalledWith(
|
||||
'usr_test123',
|
||||
'usr_owner',
|
||||
'https://example.com/avatar.png'
|
||||
);
|
||||
});
|
||||
|
||||
it('Show Fallback Avatar Details: should call showAvatarDialog with fallback', () => {
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Show Fallback Avatar Details');
|
||||
expect(deps.showAvatarDialog).toHaveBeenCalledWith('avtr_fallback');
|
||||
});
|
||||
|
||||
it('Show Fallback Avatar Details: should toast error when no fallback', () => {
|
||||
userDialog.value.ref.fallbackAvatar = null;
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Show Fallback Avatar Details');
|
||||
expect(deps.toast.error).toHaveBeenCalledWith(
|
||||
'No fallback avatar set'
|
||||
);
|
||||
});
|
||||
|
||||
it('Send Boop: should call showSendBoopDialog', () => {
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Send Boop');
|
||||
expect(deps.showSendBoopDialog).toHaveBeenCalledWith('usr_test123');
|
||||
});
|
||||
|
||||
it('Group Moderation: should call showModerateGroupDialog', () => {
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Group Moderation');
|
||||
expect(deps.showModerateGroupDialog).toHaveBeenCalledWith(
|
||||
'usr_test123'
|
||||
);
|
||||
});
|
||||
|
||||
it('Manage Gallery: should hide dialog and show gallery', () => {
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Manage Gallery');
|
||||
expect(userDialog.value.visible).toBe(false);
|
||||
expect(deps.showGalleryPage).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Previous Instances: should call instanceStore', () => {
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Previous Instances');
|
||||
expect(
|
||||
deps.instanceStore.showPreviousInstancesListDialog
|
||||
).toHaveBeenCalledWith('user', userDialog.value.ref);
|
||||
});
|
||||
|
||||
it('Invite To Group: should set invite group dialog state', () => {
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Invite To Group');
|
||||
expect(deps.inviteGroupDialog.value.groupId).toBe('');
|
||||
expect(deps.inviteGroupDialog.value.userId).toBe('usr_test123');
|
||||
expect(deps.inviteGroupDialog.value.visible).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('userDialogCommand — string callback commands', () => {
|
||||
it('should delegate string-type commands to registered callbacks', () => {
|
||||
const showSocialStatusDialog = vi.fn();
|
||||
const { userDialogCommand, registerCallbacks } =
|
||||
useUserDialogCommands(userDialog, deps);
|
||||
registerCallbacks({ showSocialStatusDialog });
|
||||
userDialogCommand('Edit Social Status');
|
||||
expect(showSocialStatusDialog).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not throw when callback is not registered', () => {
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
expect(() => userDialogCommand('Edit Bio')).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('userDialogCommand — confirmed commands', () => {
|
||||
it('Delete Favorite: should confirm then delete', async () => {
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Delete Favorite');
|
||||
await vi.waitFor(() => {
|
||||
expect(deps.modalStore.confirm).toHaveBeenCalled();
|
||||
});
|
||||
await vi.waitFor(() => {
|
||||
expect(favoriteRequest.deleteFavorite).toHaveBeenCalledWith({
|
||||
objectId: 'usr_test123'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('confirmed command should not execute when user cancels', async () => {
|
||||
deps.modalStore.confirm = vi.fn(() =>
|
||||
Promise.resolve({ ok: false })
|
||||
);
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Delete Favorite');
|
||||
await vi.waitFor(() => {
|
||||
expect(deps.modalStore.confirm).toHaveBeenCalled();
|
||||
});
|
||||
expect(favoriteRequest.deleteFavorite).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Send Friend Request: should confirm then send', async () => {
|
||||
friendRequest.sendFriendRequest.mockResolvedValue({
|
||||
params: { userId: 'usr_test123' },
|
||||
json: { success: true }
|
||||
});
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Send Friend Request');
|
||||
await vi.waitFor(() => {
|
||||
expect(friendRequest.sendFriendRequest).toHaveBeenCalledWith({
|
||||
userId: 'usr_test123'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Cancel Friend Request: should confirm then cancel', async () => {
|
||||
friendRequest.cancelFriendRequest.mockResolvedValue({
|
||||
params: { userId: 'usr_test123' },
|
||||
json: {}
|
||||
});
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Cancel Friend Request');
|
||||
await vi.waitFor(() => {
|
||||
expect(friendRequest.cancelFriendRequest).toHaveBeenCalledWith({
|
||||
userId: 'usr_test123'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Moderation Block: should confirm then send moderation', async () => {
|
||||
playerModerationRequest.sendPlayerModeration.mockResolvedValue({
|
||||
json: {
|
||||
targetUserId: 'usr_test123',
|
||||
sourceUserId: 'usr_current',
|
||||
type: 'block'
|
||||
}
|
||||
});
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Moderation Block');
|
||||
await vi.waitFor(() => {
|
||||
expect(
|
||||
playerModerationRequest.sendPlayerModeration
|
||||
).toHaveBeenCalledWith({
|
||||
moderated: 'usr_test123',
|
||||
type: 'block'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Moderation Unblock: should confirm then delete moderation', async () => {
|
||||
playerModerationRequest.deletePlayerModeration.mockResolvedValue(
|
||||
{}
|
||||
);
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Moderation Unblock');
|
||||
await vi.waitFor(() => {
|
||||
expect(
|
||||
playerModerationRequest.deletePlayerModeration
|
||||
).toHaveBeenCalledWith({
|
||||
moderated: 'usr_test123',
|
||||
type: 'block'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Report Hacking: should confirm then report', async () => {
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Report Hacking');
|
||||
await vi.waitFor(() => {
|
||||
expect(miscRequest.reportUser).toHaveBeenCalledWith({
|
||||
userId: 'usr_test123',
|
||||
contentType: 'user',
|
||||
reason: 'behavior-hacking',
|
||||
type: 'report'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Unfriend: should confirm then delete friend', async () => {
|
||||
friendRequest.deleteFriend.mockResolvedValue({});
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Unfriend');
|
||||
await vi.waitFor(() => {
|
||||
expect(friendRequest.deleteFriend).toHaveBeenCalledWith(
|
||||
{ userId: 'usr_test123' },
|
||||
'dialog.user.actions.unfriend_success_msg'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('invite dialog state', () => {
|
||||
it('Request Invite Message: should open send invite request dialog', () => {
|
||||
const {
|
||||
userDialogCommand,
|
||||
sendInviteRequestDialogVisible,
|
||||
sendInviteDialog
|
||||
} = useUserDialogCommands(userDialog, deps);
|
||||
userDialogCommand('Request Invite Message');
|
||||
expect(sendInviteRequestDialogVisible.value).toBe(true);
|
||||
expect(sendInviteDialog.value.userId).toBe('usr_test123');
|
||||
expect(deps.refreshInviteMessageTableData).toHaveBeenCalledWith(
|
||||
'request'
|
||||
);
|
||||
expect(deps.clearInviteImageUpload).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleSendFriendRequest (internal)', () => {
|
||||
it('should add friend log and update dialog state on success', async () => {
|
||||
friendRequest.sendFriendRequest.mockResolvedValue({
|
||||
params: { userId: 'usr_test123' },
|
||||
json: { success: true }
|
||||
});
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Send Friend Request');
|
||||
await vi.waitFor(() => {
|
||||
expect(database.addFriendLogHistory).toHaveBeenCalled();
|
||||
});
|
||||
expect(userDialog.value.isFriend).toBe(true);
|
||||
});
|
||||
|
||||
it('should set outgoingRequest when not success', async () => {
|
||||
friendRequest.sendFriendRequest.mockResolvedValue({
|
||||
params: { userId: 'usr_test123' },
|
||||
json: { success: false }
|
||||
});
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Send Friend Request');
|
||||
await vi.waitFor(() => {
|
||||
expect(database.addFriendLogHistory).toHaveBeenCalled();
|
||||
});
|
||||
expect(userDialog.value.outgoingRequest).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleSendPlayerModeration (internal)', () => {
|
||||
it('should update isBlock when moderation type is block', async () => {
|
||||
deps.applyPlayerModeration = vi.fn(() => ({
|
||||
targetUserId: 'usr_test123',
|
||||
sourceUserId: 'usr_current',
|
||||
type: 'block'
|
||||
}));
|
||||
playerModerationRequest.sendPlayerModeration.mockResolvedValue({
|
||||
json: {
|
||||
targetUserId: 'usr_test123',
|
||||
sourceUserId: 'usr_current',
|
||||
type: 'block'
|
||||
}
|
||||
});
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Moderation Block');
|
||||
await vi.waitFor(() => {
|
||||
expect(userDialog.value.isBlock).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should update isMute when moderation type is mute', async () => {
|
||||
deps.applyPlayerModeration = vi.fn(() => ({
|
||||
targetUserId: 'usr_test123',
|
||||
sourceUserId: 'usr_current',
|
||||
type: 'mute'
|
||||
}));
|
||||
playerModerationRequest.sendPlayerModeration.mockResolvedValue({
|
||||
json: {
|
||||
targetUserId: 'usr_test123',
|
||||
sourceUserId: 'usr_current',
|
||||
type: 'mute'
|
||||
}
|
||||
});
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
userDialogCommand('Moderation Mute');
|
||||
await vi.waitFor(() => {
|
||||
expect(userDialog.value.isMute).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unknown command', () => {
|
||||
it('should do nothing for unknown commands', () => {
|
||||
const { userDialogCommand } = useUserDialogCommands(
|
||||
userDialog,
|
||||
deps
|
||||
);
|
||||
expect(() => userDialogCommand('NonExistentCommand')).not.toThrow();
|
||||
expect(deps.modalStore.confirm).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
637
src/components/dialogs/UserDialog/useUserDialogCommands.js
Normal file
637
src/components/dialogs/UserDialog/useUserDialogCommands.js
Normal file
@@ -0,0 +1,637 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
import {
|
||||
favoriteRequest,
|
||||
friendRequest,
|
||||
miscRequest,
|
||||
notificationRequest,
|
||||
playerModerationRequest,
|
||||
worldRequest
|
||||
} from '../../../api';
|
||||
import { copyToClipboard, parseLocation } from '../../../shared/utils';
|
||||
import { database } from '../../../service/database';
|
||||
|
||||
/**
|
||||
* Composable for UserDialog command dispatch.
|
||||
* Uses a command map pattern instead of if-else/switch-case chains.
|
||||
* @param {import('vue').Ref} userDialog - reactive ref to the user dialog state
|
||||
* @param {object} deps - external dependencies
|
||||
* @param deps.t
|
||||
* @param deps.toast
|
||||
* @param deps.modalStore
|
||||
* @param deps.currentUser
|
||||
* @param deps.cachedUsers
|
||||
* @param deps.friendLogTable
|
||||
* @param deps.lastLocation
|
||||
* @param deps.lastLocationDestination
|
||||
* @param deps.inviteGroupDialog
|
||||
* @param deps.showUserDialog
|
||||
* @param deps.showFavoriteDialog
|
||||
* @param deps.showAvatarDialog
|
||||
* @param deps.showAvatarAuthorDialog
|
||||
* @param deps.showModerateGroupDialog
|
||||
* @param deps.showSendBoopDialog
|
||||
* @param deps.showGalleryPage
|
||||
* @param deps.getFriendRequest
|
||||
* @param deps.handleFriendDelete
|
||||
* @param deps.applyPlayerModeration
|
||||
* @param deps.handlePlayerModerationDelete
|
||||
* @param deps.refreshInviteMessageTableData
|
||||
* @param deps.clearInviteImageUpload
|
||||
* @param deps.instanceStore
|
||||
* @param deps.useNotificationStore
|
||||
* @returns {object} command composable API
|
||||
*/
|
||||
export function useUserDialogCommands(
|
||||
userDialog,
|
||||
{
|
||||
t,
|
||||
toast,
|
||||
modalStore,
|
||||
currentUser,
|
||||
cachedUsers,
|
||||
friendLogTable,
|
||||
lastLocation,
|
||||
lastLocationDestination,
|
||||
inviteGroupDialog,
|
||||
showUserDialog,
|
||||
showFavoriteDialog,
|
||||
showAvatarDialog,
|
||||
showAvatarAuthorDialog,
|
||||
showModerateGroupDialog,
|
||||
showSendBoopDialog,
|
||||
showGalleryPage,
|
||||
getFriendRequest,
|
||||
handleFriendDelete,
|
||||
applyPlayerModeration,
|
||||
handlePlayerModerationDelete,
|
||||
refreshInviteMessageTableData,
|
||||
clearInviteImageUpload,
|
||||
instanceStore,
|
||||
useNotificationStore
|
||||
}
|
||||
) {
|
||||
// --- Invite dialog state ---
|
||||
const sendInviteDialogVisible = ref(false);
|
||||
const sendInviteDialog = ref({
|
||||
messageSlot: {},
|
||||
userId: '',
|
||||
params: {}
|
||||
});
|
||||
const sendInviteRequestDialogVisible = ref(false);
|
||||
|
||||
// --- Internal helpers ---
|
||||
|
||||
/**
|
||||
* @param {object} args
|
||||
*/
|
||||
function handleSendFriendRequest(args) {
|
||||
const ref = cachedUsers.get(args.params.userId);
|
||||
if (typeof ref === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const friendLogHistory = {
|
||||
created_at: new Date().toJSON(),
|
||||
type: 'FriendRequest',
|
||||
userId: ref.id,
|
||||
displayName: ref.displayName
|
||||
};
|
||||
friendLogTable.value.data.push(friendLogHistory);
|
||||
database.addFriendLogHistory(friendLogHistory);
|
||||
|
||||
const D = userDialog.value;
|
||||
if (D.visible === false || D.id !== args.params.userId) {
|
||||
return;
|
||||
}
|
||||
if (args.json.success) {
|
||||
D.isFriend = true;
|
||||
} else {
|
||||
D.outgoingRequest = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} args
|
||||
*/
|
||||
function handleCancelFriendRequest(args) {
|
||||
const ref = cachedUsers.get(args.params.userId);
|
||||
if (typeof ref === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const friendLogHistory = {
|
||||
created_at: new Date().toJSON(),
|
||||
type: 'CancelFriendRequest',
|
||||
userId: ref.id,
|
||||
displayName: ref.displayName
|
||||
};
|
||||
friendLogTable.value.data.push(friendLogHistory);
|
||||
database.addFriendLogHistory(friendLogHistory);
|
||||
const D = userDialog.value;
|
||||
if (D.visible === false || D.id !== args.params.userId) {
|
||||
return;
|
||||
}
|
||||
D.outgoingRequest = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} args
|
||||
*/
|
||||
function handleSendPlayerModeration(args) {
|
||||
const ref = applyPlayerModeration(args.json);
|
||||
const D = userDialog.value;
|
||||
if (
|
||||
D.visible === false ||
|
||||
(ref.targetUserId !== D.id &&
|
||||
ref.sourceUserId !== currentUser.value.id)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (ref.type === 'block') {
|
||||
D.isBlock = true;
|
||||
} else if (ref.type === 'mute') {
|
||||
D.isMute = true;
|
||||
} else if (ref.type === 'interactOff') {
|
||||
D.isInteractOff = true;
|
||||
} else if (ref.type === 'muteChat') {
|
||||
D.isMuteChat = true;
|
||||
}
|
||||
toast.success(t('message.user.moderated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} userId
|
||||
* @param {number} type
|
||||
*/
|
||||
function setPlayerModeration(userId, type) {
|
||||
const D = userDialog.value;
|
||||
AppApi.SetVRChatUserModeration(currentUser.value.id, userId, type).then(
|
||||
(result) => {
|
||||
if (result) {
|
||||
if (type === 4) {
|
||||
D.isShowAvatar = false;
|
||||
D.isHideAvatar = true;
|
||||
} else if (type === 5) {
|
||||
D.isShowAvatar = true;
|
||||
D.isHideAvatar = false;
|
||||
} else {
|
||||
D.isShowAvatar = false;
|
||||
D.isHideAvatar = false;
|
||||
}
|
||||
} else {
|
||||
toast.error(t('message.avatar.change_moderation_failed'));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {string} userId
|
||||
*/
|
||||
function showSendInviteDialogFn(params, userId) {
|
||||
sendInviteDialog.value = {
|
||||
params,
|
||||
userId,
|
||||
messageSlot: {}
|
||||
};
|
||||
refreshInviteMessageTableData('message');
|
||||
clearInviteImageUpload();
|
||||
sendInviteDialogVisible.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {string} userId
|
||||
*/
|
||||
function showSendInviteRequestDialogFn(params, userId) {
|
||||
sendInviteDialog.value = {
|
||||
params,
|
||||
userId,
|
||||
messageSlot: {}
|
||||
};
|
||||
refreshInviteMessageTableData('request');
|
||||
clearInviteImageUpload();
|
||||
sendInviteRequestDialogVisible.value = true;
|
||||
}
|
||||
|
||||
// --- Command map ---
|
||||
// Direct commands: function
|
||||
// Confirmed commands: { confirm: true, handler: fn }
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function buildCommandMap() {
|
||||
const D = () => userDialog.value;
|
||||
|
||||
return {
|
||||
// --- Direct commands ---
|
||||
Refresh: () => {
|
||||
const userId = D().id;
|
||||
D().id = '';
|
||||
showUserDialog(userId);
|
||||
},
|
||||
Share: () => {
|
||||
copyToClipboard(
|
||||
`https://vrchat.com/home/user/${D().id}`,
|
||||
'User URL copied to clipboard'
|
||||
);
|
||||
},
|
||||
'Add Favorite': () => {
|
||||
showFavoriteDialog('friend', D().id);
|
||||
},
|
||||
'Edit Social Status': 'showSocialStatusDialog',
|
||||
'Edit Language': 'showLanguageDialog',
|
||||
'Edit Bio': 'showBioDialog',
|
||||
'Edit Pronouns': 'showPronounsDialog',
|
||||
'Request Invite': () => {
|
||||
notificationRequest
|
||||
.sendRequestInvite(
|
||||
{
|
||||
platform: 'standalonewindows'
|
||||
},
|
||||
D().id
|
||||
)
|
||||
.then((args) => {
|
||||
toast('Request invite sent');
|
||||
return args;
|
||||
});
|
||||
},
|
||||
'Invite Message': () => {
|
||||
const L = parseLocation(lastLocation.value.location);
|
||||
worldRequest
|
||||
.getCachedWorld({
|
||||
worldId: L.worldId
|
||||
})
|
||||
.then((args) => {
|
||||
showSendInviteDialogFn(
|
||||
{
|
||||
instanceId: lastLocation.value.location,
|
||||
worldId: lastLocation.value.location,
|
||||
worldName: args.ref.name
|
||||
},
|
||||
D().id
|
||||
);
|
||||
});
|
||||
},
|
||||
'Request Invite Message': () => {
|
||||
showSendInviteRequestDialogFn(
|
||||
{
|
||||
platform: 'standalonewindows'
|
||||
},
|
||||
D().id
|
||||
);
|
||||
},
|
||||
Invite: () => {
|
||||
let currentLocation = lastLocation.value.location;
|
||||
if (lastLocation.value.location === 'traveling') {
|
||||
currentLocation = lastLocationDestination.value;
|
||||
}
|
||||
const L = parseLocation(currentLocation);
|
||||
worldRequest
|
||||
.getCachedWorld({
|
||||
worldId: L.worldId
|
||||
})
|
||||
.then((args) => {
|
||||
notificationRequest
|
||||
.sendInvite(
|
||||
{
|
||||
instanceId: L.tag,
|
||||
worldId: L.tag,
|
||||
worldName: args.ref.name
|
||||
},
|
||||
D().id
|
||||
)
|
||||
.then((_args) => {
|
||||
toast(t('message.invite.sent'));
|
||||
return _args;
|
||||
});
|
||||
});
|
||||
},
|
||||
'Show Avatar Author': () => {
|
||||
const { currentAvatarImageUrl } = D().ref;
|
||||
showAvatarAuthorDialog(
|
||||
D().id,
|
||||
D().$avatarInfo.ownerId,
|
||||
currentAvatarImageUrl
|
||||
);
|
||||
},
|
||||
'Show Fallback Avatar Details': () => {
|
||||
const { fallbackAvatar } = D().ref;
|
||||
if (fallbackAvatar) {
|
||||
showAvatarDialog(fallbackAvatar);
|
||||
} else {
|
||||
toast.error('No fallback avatar set');
|
||||
}
|
||||
},
|
||||
'Previous Instances': () => {
|
||||
instanceStore.showPreviousInstancesListDialog('user', D().ref);
|
||||
},
|
||||
'Manage Gallery': () => {
|
||||
userDialog.value.visible = false;
|
||||
showGalleryPage();
|
||||
},
|
||||
'Invite To Group': () => {
|
||||
inviteGroupDialog.value.groupId = '';
|
||||
inviteGroupDialog.value.userId = D().id;
|
||||
inviteGroupDialog.value.visible = true;
|
||||
},
|
||||
'Send Boop': () => {
|
||||
showSendBoopDialog(D().id);
|
||||
},
|
||||
'Group Moderation': () => {
|
||||
showModerateGroupDialog(D().id);
|
||||
},
|
||||
'Hide Avatar': () => {
|
||||
if (D().isHideAvatar) {
|
||||
setPlayerModeration(D().id, 0);
|
||||
} else {
|
||||
setPlayerModeration(D().id, 4);
|
||||
}
|
||||
},
|
||||
'Show Avatar': () => {
|
||||
if (D().isShowAvatar) {
|
||||
setPlayerModeration(D().id, 0);
|
||||
} else {
|
||||
setPlayerModeration(D().id, 5);
|
||||
}
|
||||
},
|
||||
'Edit Note Memo': 'showEditNoteAndMemoDialog',
|
||||
|
||||
// --- Confirmed commands ---
|
||||
'Delete Favorite': {
|
||||
confirm: true,
|
||||
handler: (userId) => {
|
||||
favoriteRequest.deleteFavorite({
|
||||
objectId: userId
|
||||
});
|
||||
}
|
||||
},
|
||||
'Accept Friend Request': {
|
||||
confirm: true,
|
||||
handler: async (userId) => {
|
||||
const key = getFriendRequest(userId);
|
||||
if (key === '') {
|
||||
const args = await friendRequest.sendFriendRequest({
|
||||
userId
|
||||
});
|
||||
handleSendFriendRequest(args);
|
||||
} else {
|
||||
notificationRequest
|
||||
.acceptFriendRequestNotification({
|
||||
notificationId: key
|
||||
})
|
||||
.then((args) => {
|
||||
useNotificationStore().handleNotificationAccept(
|
||||
args
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (
|
||||
err &&
|
||||
err.message &&
|
||||
err.message.includes('404')
|
||||
) {
|
||||
useNotificationStore().handleNotificationHide(
|
||||
key
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
'Decline Friend Request': {
|
||||
confirm: true,
|
||||
handler: async (userId) => {
|
||||
const key = getFriendRequest(userId);
|
||||
if (key === '') {
|
||||
const args = await friendRequest.cancelFriendRequest({
|
||||
userId
|
||||
});
|
||||
handleCancelFriendRequest(args);
|
||||
} else {
|
||||
notificationRequest
|
||||
.hideNotification({
|
||||
notificationId: key
|
||||
})
|
||||
.then(() => {
|
||||
useNotificationStore().handleNotificationHide(
|
||||
key
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
'Cancel Friend Request': {
|
||||
confirm: true,
|
||||
handler: async (userId) => {
|
||||
const args = await friendRequest.cancelFriendRequest({
|
||||
userId
|
||||
});
|
||||
handleCancelFriendRequest(args);
|
||||
}
|
||||
},
|
||||
'Send Friend Request': {
|
||||
confirm: true,
|
||||
handler: async (userId) => {
|
||||
const args = await friendRequest.sendFriendRequest({
|
||||
userId
|
||||
});
|
||||
handleSendFriendRequest(args);
|
||||
}
|
||||
},
|
||||
'Moderation Unblock': {
|
||||
confirm: true,
|
||||
handler: async (userId) => {
|
||||
const args =
|
||||
await playerModerationRequest.deletePlayerModeration({
|
||||
moderated: userId,
|
||||
type: 'block'
|
||||
});
|
||||
handlePlayerModerationDelete(args);
|
||||
}
|
||||
},
|
||||
'Moderation Block': {
|
||||
confirm: true,
|
||||
handler: async (userId) => {
|
||||
const args =
|
||||
await playerModerationRequest.sendPlayerModeration({
|
||||
moderated: userId,
|
||||
type: 'block'
|
||||
});
|
||||
handleSendPlayerModeration(args);
|
||||
}
|
||||
},
|
||||
'Moderation Unmute': {
|
||||
confirm: true,
|
||||
handler: async (userId) => {
|
||||
const args =
|
||||
await playerModerationRequest.deletePlayerModeration({
|
||||
moderated: userId,
|
||||
type: 'mute'
|
||||
});
|
||||
handlePlayerModerationDelete(args);
|
||||
}
|
||||
},
|
||||
'Moderation Mute': {
|
||||
confirm: true,
|
||||
handler: async (userId) => {
|
||||
const args =
|
||||
await playerModerationRequest.sendPlayerModeration({
|
||||
moderated: userId,
|
||||
type: 'mute'
|
||||
});
|
||||
handleSendPlayerModeration(args);
|
||||
}
|
||||
},
|
||||
'Moderation Enable Avatar Interaction': {
|
||||
confirm: true,
|
||||
handler: async (userId) => {
|
||||
const args =
|
||||
await playerModerationRequest.deletePlayerModeration({
|
||||
moderated: userId,
|
||||
type: 'interactOff'
|
||||
});
|
||||
handlePlayerModerationDelete(args);
|
||||
}
|
||||
},
|
||||
'Moderation Disable Avatar Interaction': {
|
||||
confirm: true,
|
||||
handler: async (userId) => {
|
||||
const args =
|
||||
await playerModerationRequest.sendPlayerModeration({
|
||||
moderated: userId,
|
||||
type: 'interactOff'
|
||||
});
|
||||
handleSendPlayerModeration(args);
|
||||
}
|
||||
},
|
||||
'Moderation Enable Chatbox': {
|
||||
confirm: true,
|
||||
handler: async (userId) => {
|
||||
const args =
|
||||
await playerModerationRequest.deletePlayerModeration({
|
||||
moderated: userId,
|
||||
type: 'muteChat'
|
||||
});
|
||||
handlePlayerModerationDelete(args);
|
||||
}
|
||||
},
|
||||
'Moderation Disable Chatbox': {
|
||||
confirm: true,
|
||||
handler: async (userId) => {
|
||||
const args =
|
||||
await playerModerationRequest.sendPlayerModeration({
|
||||
moderated: userId,
|
||||
type: 'muteChat'
|
||||
});
|
||||
handleSendPlayerModeration(args);
|
||||
}
|
||||
},
|
||||
'Report Hacking': {
|
||||
confirm: true,
|
||||
handler: (userId) => {
|
||||
miscRequest.reportUser({
|
||||
userId,
|
||||
contentType: 'user',
|
||||
reason: 'behavior-hacking',
|
||||
type: 'report'
|
||||
});
|
||||
}
|
||||
},
|
||||
Unfriend: {
|
||||
confirm: true,
|
||||
handler: async (userId) => {
|
||||
const args = await friendRequest.deleteFriend(
|
||||
{
|
||||
userId
|
||||
},
|
||||
t('dialog.user.actions.unfriend_success_msg')
|
||||
);
|
||||
handleFriendDelete(args);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const commandMap = buildCommandMap();
|
||||
|
||||
// Callbacks for string-type commands (delegated to component)
|
||||
let componentCallbacks = {};
|
||||
|
||||
/**
|
||||
* Register component-level callbacks for string-type commands.
|
||||
* These are simple dialog openers that stay in the component.
|
||||
* @param {object} callbacks
|
||||
*/
|
||||
function registerCallbacks(callbacks) {
|
||||
componentCallbacks = callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a user dialog command.
|
||||
* @param {string} command
|
||||
*/
|
||||
function userDialogCommand(command) {
|
||||
const D = userDialog.value;
|
||||
if (D.visible === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entry = commandMap[command];
|
||||
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
// String entry => delegate to component callback
|
||||
if (typeof entry === 'string') {
|
||||
const cb = componentCallbacks[entry];
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Direct function
|
||||
if (typeof entry === 'function') {
|
||||
entry();
|
||||
return;
|
||||
}
|
||||
|
||||
// Confirmed command
|
||||
if (entry.confirm) {
|
||||
const i18nPreFix = 'dialog.user.actions.';
|
||||
const formattedCommand = command.toLowerCase().replace(/ /g, '_');
|
||||
const displayCommandText = t(
|
||||
`${i18nPreFix}${formattedCommand}`
|
||||
).includes('i18nPreFix')
|
||||
? command
|
||||
: t(`${i18nPreFix}${formattedCommand}`);
|
||||
|
||||
modalStore
|
||||
.confirm({
|
||||
description: t('confirm.message', {
|
||||
command: displayCommandText
|
||||
}),
|
||||
title: t('confirm.title'),
|
||||
confirmText: t('confirm.confirm_button'),
|
||||
cancelText: t('confirm.cancel_button')
|
||||
})
|
||||
.then(({ ok }) => {
|
||||
if (ok) {
|
||||
entry.handler(D.id);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sendInviteDialogVisible,
|
||||
sendInviteDialog,
|
||||
sendInviteRequestDialogVisible,
|
||||
userDialogCommand,
|
||||
registerCallbacks
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user