This commit is contained in:
pa
2026-03-09 02:49:59 +09:00
parent 64b27ce7f1
commit 90a17bb0ba
39 changed files with 9487 additions and 4384 deletions

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