This commit is contained in:
Natsumi
2025-10-17 16:57:09 +11:00
parent 9e95e1734c
commit dc51d156e4
13 changed files with 386 additions and 330 deletions

View File

@@ -611,6 +611,7 @@
} from '../../../shared/utils';
import { useAvatarStore, useFavoriteStore, useGalleryStore, useGameStore, useUserStore } from '../../../stores';
import { avatarModerationRequest, avatarRequest, favoriteRequest, miscRequest } from '../../../api';
import { AppDebug } from '../../../service/appConfig.js';
import { database } from '../../../service/database';
import { getNextDialogIndex } from '../../../shared/utils/base/ui';
import { handleImageUploadInput } from '../../../shared/utils/imageUpload';
@@ -727,7 +728,7 @@
}
function getImageUrlFromImageId(imageId) {
return `https://api.vrchat.cloud/api/1/file/${imageId}/1/`;
return `${AppDebug.endpointDomain}/file/${imageId}/1/`;
}
function handleDialogOpen() {

View File

@@ -1,277 +1,250 @@
<!--<template>-->
<!-- <el-dialog-->
<!-- class="x-dialog"-->
<!-- :model-value="sendBoopDialog.visible"-->
<!-- :title="t('dialog.boop_dialog.header')"-->
<!-- width="450px"-->
<!-- @close="closeDialog">-->
<!-- <el-select-->
<!-- v-model="sendBoopDialog.userId"-->
<!-- :placeholder="t('dialog.new_instance.instance_creator_placeholder')"-->
<!-- filterable-->
<!-- style="width: 100%">-->
<!-- <el-option-group v-if="vipFriends.length" :label="t('side_panel.favorite')">-->
<!-- <el-option-->
<!-- v-for="friend in vipFriends"-->
<!-- :key="friend.id"-->
<!-- class="x-friend-item"-->
<!-- :label="friend.name"-->
<!-- :value="friend.id"-->
<!-- style="height: auto">-->
<!-- <template v-if="friend.ref">-->
<!-- <div class="avatar" :class="userStatusClass(friend.ref)">-->
<!-- <img :src="userImage(friend.ref)" loading="lazy">-->
<!-- </div>-->
<!-- <div class="detail">-->
<!-- <span-->
<!-- class="name"-->
<!-- :style="{ color: friend.ref.$userColour }"-->
<!-- v-text="friend.ref.displayName"></span>-->
<!-- </div>-->
<!-- </template>-->
<!-- <span v-else v-text="friend.id"></span>-->
<!-- </el-option>-->
<!-- </el-option-group>-->
<!-- <el-option-group v-if="onlineFriends.length" :label="t('side_panel.online')">-->
<!-- <el-option-->
<!-- v-for="friend in onlineFriends"-->
<!-- :key="friend.id"-->
<!-- class="x-friend-item"-->
<!-- :label="friend.name"-->
<!-- :value="friend.id"-->
<!-- style="height: auto">-->
<!-- <template v-if="friend.ref">-->
<!-- <div class="avatar" :class="userStatusClass(friend.ref)">-->
<!-- <img :src="userImage(friend.ref)" loading="lazy">-->
<!-- </div>-->
<!-- <div class="detail">-->
<!-- <span-->
<!-- class="name"-->
<!-- :style="{ color: friend.ref.$userColour }"-->
<!-- v-text="friend.ref.displayName"></span>-->
<!-- </div>-->
<!-- </template>-->
<!-- <span v-else v-text="friend.id"></span>-->
<!-- </el-option>-->
<!-- </el-option-group>-->
<!-- <el-option-group v-if="activeFriends.length" :label="t('side_panel.active')">-->
<!-- <el-option-->
<!-- v-for="friend in activeFriends"-->
<!-- :key="friend.id"-->
<!-- class="x-friend-item"-->
<!-- :label="friend.name"-->
<!-- :value="friend.id"-->
<!-- style="height: auto">-->
<!-- <template v-if="friend.ref">-->
<!-- <div class="avatar">-->
<!-- <img :src="userImage(friend.ref)" loading="lazy">-->
<!-- </div>-->
<!-- <div class="detail">-->
<!-- <span-->
<!-- class="name"-->
<!-- :style="{ color: friend.ref.$userColour }"-->
<!-- v-text="friend.ref.displayName"></span>-->
<!-- </div>-->
<!-- </template>-->
<!-- <span v-else v-text="friend.id"></span>-->
<!-- </el-option>-->
<!-- </el-option-group>-->
<!-- <el-option-group v-if="offlineFriends.length" :label="t('side_panel.offline')">-->
<!-- <el-option-->
<!-- v-for="friend in offlineFriends"-->
<!-- :key="friend.id"-->
<!-- class="x-friend-item"-->
<!-- :label="friend.name"-->
<!-- :value="friend.id"-->
<!-- style="height: auto">-->
<!-- <template v-if="friend.ref">-->
<!-- <div class="avatar">-->
<!-- <img :src="userImage(friend.ref)" loading="lazy">-->
<!-- </div>-->
<!-- <div class="detail">-->
<!-- <span-->
<!-- class="name"-->
<!-- :style="{ color: friend.ref.$userColour }"-->
<!-- v-text="friend.ref.displayName"></span>-->
<!-- </div>-->
<!-- </template>-->
<!-- <span v-else v-text="friend.id"></span>-->
<!-- </el-option>-->
<!-- </el-option-group>-->
<!-- </el-select>-->
<template>
<el-dialog
class="x-dialog"
v-model="sendBoopDialog.visible"
:title="t('dialog.boop_dialog.header')"
width="450px"
@close="closeDialog">
<el-select
v-if="sendBoopDialog.visible"
v-model="sendBoopDialog.userId"
:placeholder="t('dialog.new_instance.instance_creator_placeholder')"
filterable
style="width: 100%">
<el-option-group v-if="vipFriends.length" :label="t('side_panel.favorite')">
<el-option
v-for="friend in vipFriends"
:key="friend.id"
:label="friend.name"
:value="friend.id"
style="height: auto"
class="x-friend-item">
<template v-if="friend.ref">
<div class="avatar" :class="userStatusClass(friend.ref)">
<img :src="userImage(friend.ref)" loading="lazy" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: friend.ref.$userColour }"
v-text="friend.ref.displayName"></span>
</div>
</template>
<span v-else v-text="friend.id"></span>
</el-option>
</el-option-group>
<el-option-group v-if="onlineFriends.length" :label="t('side_panel.online')">
<el-option
v-for="friend in onlineFriends"
:key="friend.id"
:label="friend.name"
:value="friend.id"
style="height: auto"
class="x-friend-item">
<template v-if="friend.ref">
<div class="avatar" :class="userStatusClass(friend.ref)">
<img :src="userImage(friend.ref)" loading="lazy" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: friend.ref.$userColour }"
v-text="friend.ref.displayName"></span>
</div>
</template>
<span v-else v-text="friend.id"></span>
</el-option>
</el-option-group>
<el-option-group v-if="activeFriends.length" :label="t('side_panel.active')">
<el-option
v-for="friend in activeFriends"
:key="friend.id"
:label="friend.name"
:value="friend.id"
style="height: auto"
class="x-friend-item">
<template v-if="friend.ref">
<div class="avatar">
<img :src="userImage(friend.ref)" loading="lazy" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: friend.ref.$userColour }"
v-text="friend.ref.displayName"></span>
</div>
</template>
<span v-else v-text="friend.id"></span>
</el-option>
</el-option-group>
<el-option-group v-if="offlineFriends.length" :label="t('side_panel.offline')">
<el-option
v-for="friend in offlineFriends"
:key="friend.id"
:label="friend.name"
:value="friend.id"
style="height: auto"
class="x-friend-item">
<template v-if="friend.ref">
<div class="avatar">
<img :src="userImage(friend.ref)" loading="lazy" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: friend.ref.$userColour }"
v-text="friend.ref.displayName"></span>
</div>
</template>
<span v-else v-text="friend.id"></span>
</el-option>
</el-option-group>
</el-select>
<!-- <br />-->
<!-- <br />-->
<br />
<br />
<!-- <el-select-->
<!-- v-model="fileId"-->
<!-- clearable-->
<!-- :placeholder="t('dialog.boop_dialog.select_emoji')"-->
<!-- size="small"-->
<!-- style="width: 100%"-->
<!-- popper-class="max-height-el-select">-->
<!-- <el-option-group :label="t('dialog.boop_dialog.my_emojis')">-->
<!-- <el-option-->
<!-- v-for="image in emojiTable"-->
<!-- v-if="image.versions && image.versions.length > 0"-->
<!-- :key="image.id"-->
<!-- :value="image.id"-->
<!-- style="width: 100%; height: 100%">-->
<!-- <div-->
<!-- v-if="image.versions[image.versions.length - 1].file.url"-->
<!-- class="vrcplus-icon"-->
<!-- style="overflow: hidden; width: 200px; height: 200px; padding: 10px">-->
<!-- <template v-if="image.frames">-->
<!-- <div-->
<!-- class="avatar"-->
<!-- :style="-->
<!-- generateEmojiStyle(-->
<!-- image.versions[image.versions.length - 1].file.url,-->
<!-- image.framesOverTime,-->
<!-- image.frames,-->
<!-- image.loopStyle-->
<!-- )-->
<!-- "></div>-->
<!-- </template>-->
<!-- <template v-else>-->
<!-- <img-->
<!-- :src="image.versions[image.versions.length - 1].file.url"-->
<!-- class="avatar"-->
<!-- style="width: 200px; height: 200px" />-->
<!-- </template>-->
<!-- </div>-->
<!-- </el-option>-->
<!-- </el-option-group>-->
<!-- <el-option-group :label="t('dialog.boop_dialog.default_emojis')">-->
<!-- <el-option-->
<!-- v-for="emojiName in photonEmojis"-->
<!-- :key="emojiName"-->
<!-- :value="getEmojiValue(emojiName)"-->
<!-- style="width: 100%; height: 100%">-->
<!-- <span v-text="emojiName"></span>-->
<!-- </el-option>-->
<!-- </el-option-group>-->
<!-- </el-select>-->
<el-select
v-if="sendBoopDialog.visible"
v-model="fileId"
clearable
:placeholder="t('dialog.boop_dialog.select_emoji')"
size="small"
style="width: 100%"
popper-class="max-height-el-select">
<el-option-group :label="t('dialog.boop_dialog.my_emojis')">
<el-option
v-for="image in emojiTable"
:key="image.id"
:value="image.id"
style="width: 100%; height: 100%">
<div
v-if="
image.versions &&
image.versions.length > 0 &&
image.versions[image.versions.length - 1].file.url
"
class="vrcplus-icon"
style="overflow: hidden; width: 200px; height: 200px; padding: 10px">
<template v-if="image.frames">
<div
class="avatar"
:style="
generateEmojiStyle(
image.versions[image.versions.length - 1].file.url,
image.framesOverTime,
image.frames,
image.loopStyle
)
"></div>
</template>
<template v-else>
<img
:src="image.versions[image.versions.length - 1].file.url"
class="avatar"
style="width: 200px; height: 200px" />
</template>
</div>
</el-option>
</el-option-group>
<el-option-group :label="t('dialog.boop_dialog.default_emojis')">
<el-option
v-for="emojiName in photonEmojis"
:key="emojiName"
:value="getEmojiValue(emojiName)"
style="width: 100%; height: 100%">
<span v-text="emojiName"></span>
</el-option>
</el-option-group>
</el-select>
<!-- <template #footer>-->
<!-- <el-button size="small" @click="showGalleryDialog(2)">{{-->
<!-- t('dialog.boop_dialog.emoji_manager')-->
<!-- }}</el-button>-->
<!-- <el-button size="small" @click="closeDialog">{{ t('dialog.boop_dialog.cancel') }}</el-button>-->
<!-- <el-button size="small" :disabled="!sendBoopDialog.userId" @click="sendBoop">{{-->
<!-- t('dialog.boop_dialog.send')-->
<!-- }}</el-button>-->
<!-- </template>-->
<!-- </el-dialog>-->
<!--</template>-->
<template #footer>
<el-button
size="small"
@click="
redirectToToolsTab();
showGalleryDialog();
"
>{{ t('dialog.boop_dialog.emoji_manager') }}</el-button
>
<el-button size="small" @click="closeDialog">{{ t('dialog.boop_dialog.cancel') }}</el-button>
<el-button size="small" :disabled="!sendBoopDialog.userId" @click="sendBoop">{{
t('dialog.boop_dialog.send')
}}</el-button>
</template>
</el-dialog>
</template>
<!--<script setup>-->
<!-- import { inject, ref } from 'vue';-->
<!-- import { useI18n } from 'vue-i18n-bridge';-->
<!-- import { photonEmojis } from '../../composables/shared/constants/photon.js';-->
<!-- import { notificationRequest } from '../../api';-->
<!-- // import { miscRequest } from '../../api';-->
<script setup>
import { ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
<!-- const { t } = useI18n();-->
import { generateEmojiStyle, userImage, userStatusClass } from '../../shared/utils';
import { miscRequest } from '../../api';
import { notificationRequest } from '../../api';
import { photonEmojis } from '../../shared/constants/photon.js';
import { redirectToToolsTab } from '../../shared/utils/base/ui';
import { useFriendStore } from '../../stores';
import { useGalleryStore } from '../../stores';
import { useNotificationStore } from '../../stores';
import { useUserStore } from '../../stores/user.js';
<!-- const userStatusClass = inject('userStatusClass');-->
<!-- const userImage = inject('userImage');-->
<!-- const showGalleryDialog = inject('showGalleryDialog');-->
const { t } = useI18n();
<!-- const props = defineProps({-->
<!-- sendBoopDialog: {-->
<!-- type: Object,-->
<!-- required: true-->
<!-- },-->
<!-- emojiTable: {-->
<!-- type: Array,-->
<!-- required: true-->
<!-- },-->
<!-- vipFriends: {-->
<!-- type: Array,-->
<!-- required: true-->
<!-- },-->
<!-- onlineFriends: {-->
<!-- type: Array,-->
<!-- required: true-->
<!-- },-->
<!-- activeFriends: {-->
<!-- type: Array,-->
<!-- required: true-->
<!-- },-->
<!-- offlineFriends: {-->
<!-- type: Array,-->
<!-- required: true-->
<!-- },-->
<!-- generateEmojiStyle: {-->
<!-- type: Function,-->
<!-- required: true-->
<!-- },-->
<!-- notificationTable: {-->
<!-- type: Object,-->
<!-- required: true-->
<!-- }-->
<!-- });-->
const { sendBoopDialog } = storeToRefs(useUserStore());
const { notificationTable } = storeToRefs(useNotificationStore());
const { showGalleryDialog, refreshEmojiTable } = useGalleryStore();
const { emojiTable } = storeToRefs(useGalleryStore());
const { vipFriends, onlineFriends, activeFriends, offlineFriends } = useFriendStore();
<!-- const emit = defineEmits(['update:sendBoopDialog']);-->
const fileId = ref('');
<!-- const fileId = ref('');-->
watch(
() => sendBoopDialog.value.visible,
(visible) => {
if (visible && emojiTable.value.length === 0) {
refreshEmojiTable();
}
}
);
<!-- // $app.data.sendBoopDialog = {-->
<!-- // visible: false,-->
<!-- // userId: ''-->
<!-- // };-->
<!-- // $app.methods.showSendBoopDialog = function (userId) {-->
<!-- // this.$nextTick(() =>-->
<!-- // $app.adjustDialogZ(this.$refs.sendBoopDialog.$el)-->
<!-- // );-->
<!-- // const D = this.sendBoopDialog;-->
<!-- // D.userId = userId;-->
<!-- // D.visible = true;-->
<!-- // if (this.emojiTable.length === 0 && API.currentUser.$isVRCPlus) {-->
<!-- // this.refreshEmojiTable();-->
<!-- // }-->
<!-- // };-->
function closeDialog() {
sendBoopDialog.value.visible = false;
}
function getEmojiValue(emojiName) {
if (!emojiName) {
return '';
}
return `default_${emojiName.replace(/ /g, '_').toLowerCase()}`;
}
<!-- function closeDialog() {-->
<!-- emit('update:sendBoopDialog', {-->
<!-- ...props.sendBoopDialog,-->
<!-- visible: false-->
<!-- });-->
<!-- }-->
<!-- function getEmojiValue(emojiName) {-->
<!-- if (!emojiName) {-->
<!-- return '';-->
<!-- }-->
<!-- return `vrchat_${emojiName.replace(/ /g, '_').toLowerCase()}`;-->
<!-- }-->
function sendBoop() {
const D = sendBoopDialog.value;
dismissBoop(D.userId);
const params = {
userId: D.userId
};
if (fileId.value) {
params.emojiId = fileId.value;
}
miscRequest.sendBoop(params);
D.visible = false;
}
<!-- function sendBoop() {-->
<!-- const D = props.sendBoopDialog;-->
<!-- dismissBoop(D.userId);-->
<!-- const params = {-->
<!-- userId: D.userId-->
<!-- };-->
<!-- if (fileId.value) {-->
<!-- params.emojiId = fileId.value;-->
<!-- }-->
<!-- // miscRequest.sendBoop(params);-->
<!-- D.visible = false;-->
<!-- }-->
<!-- function dismissBoop(userId) {-->
<!-- // JANK: This is a hack to remove boop notifications when responding-->
<!-- const array = props.notificationTable.data;-->
<!-- for (let i = array.length - 1; i >= 0; i&#45;&#45;) {-->
<!-- const ref = array[i];-->
<!-- if (ref.type !== 'boop' || ref.$isExpired || ref.senderUserId !== userId) {-->
<!-- continue;-->
<!-- }-->
<!-- notificationRequest.sendNotificationResponse({-->
<!-- notificationId: ref.id,-->
<!-- responseType: 'delete',-->
<!-- responseData: ''-->
<!-- });-->
<!-- }-->
<!-- }-->
<!--</script>-->
function dismissBoop(userId) {
// JANK: This is a hack to remove boop notifications when responding
const array = notificationTable.value.data;
for (let i = array.length - 1; i >= 0; i--) {
const ref = array[i];
if (ref.type !== 'boop' || ref.$isExpired || ref.senderUserId !== userId) {
continue;
}
notificationRequest.sendNotificationResponse({
notificationId: ref.id,
responseType: 'delete',
responseData: ''
});
}
}
</script>

View File

@@ -108,6 +108,12 @@
<el-dropdown-item v-else :icon="Plus" command="Send Friend Request">{{
t('dialog.user.actions.send_friend_request')
}}</el-dropdown-item>
<el-dropdown-item
:disabled="!currentUser.isBoopingEnabled"
:icon="Pointer"
command="Send Boop"
>{{ t('dialog.user.actions.send_boop') }}</el-dropdown-item
>
<el-dropdown-item :icon="Message" command="Invite To Group">{{
t('dialog.user.actions.invite_to_group')
}}</el-dropdown-item>

View File

@@ -423,11 +423,17 @@
}}</span>
</div>
</div>
<!--//- .x-friend-item(@click="toggleAllowBooping")-->
<!--//- .detail-->
<!--//- span.name {{ t('dialog.user.info.booping') }}-->
<!--//- span.extra(v-if="currentUser.isBoopingEnabled" style="color:#67C23A") {{ t('dialog.user.info.avatar_cloning_allow') }}-->
<!--//- span.extra(v-else style="color:#F56C6C") {{ t('dialog.user.info.avatar_cloning_deny') }}-->
<div class="x-friend-item" @click="toggleAllowBooping">
<div class="detail">
<span class="name">{{ t('dialog.user.info.booping') }}</span>
<span v-if="currentUser.isBoopingEnabled" class="extra" style="color: #67c23a">{{
t('dialog.user.info.avatar_cloning_allow')
}}</span>
<span v-else class="extra" style="color: #f56c6c">{{
t('dialog.user.info.avatar_cloning_deny')
}}</span>
</div>
</div>
</template>
<template v-else>
<div class="x-friend-item" style="cursor: default">
@@ -1302,8 +1308,14 @@
const { hideUserNotes, hideUserMemos } = storeToRefs(useAppearanceSettingsStore());
const { avatarRemoteDatabase } = storeToRefs(useAdvancedSettingsStore());
const { userDialog, languageDialog, currentUser, isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
const { cachedUsers, showUserDialog, sortUserDialogAvatars, refreshUserDialogAvatars, refreshUserDialogTreeData } =
useUserStore();
const {
cachedUsers,
showUserDialog,
sortUserDialogAvatars,
refreshUserDialogAvatars,
refreshUserDialogTreeData,
showSendBoopDialog
} = useUserStore();
const { favoriteLimits } = storeToRefs(useFavoriteStore());
const { showFavoriteDialog, handleFavoriteWorldList } = useFavoriteStore();
const { showAvatarDialog, lookupAvatars, showAvatarAuthorDialog } = useAvatarStore();
@@ -1749,8 +1761,8 @@
redirectToToolsTab();
} else if (command === 'Invite To Group') {
showInviteGroupDialog('', D.id);
// } else if (command === 'Send Boop') {
// this.showSendBoopDialog(D.id);
} else if (command === 'Send Boop') {
showSendBoopDialog(D.id);
} else if (command === 'Group Moderation') {
showModerateGroupDialog(D.id);
} else if (command === 'Hide Avatar') {
@@ -2273,6 +2285,12 @@
});
}
function toggleAllowBooping() {
userRequest.saveCurrentUser({
isBoopingEnabled: !currentUser.value.isBoopingEnabled
});
}
function resetHome() {
ElMessageBox.confirm('Continue? Reset Home', 'Confirm', {
confirmButtonText: 'Confirm',