mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-09 18:03:49 +02:00
* refactor: dialogs * fix: storeAvatarImage * FriendLog.vue * FriendLog.vue * FriendLog.vue * GameLog.vue * fix: next day button jumping to the wrong date * sync master * fix: launchGame * Notification.vue * Feed.vue * Search.vue * Profile.vue * PlayerList.vue * Login.vue * utils * update dialog * del gameLog.pug * fix * fix: group role cannot be displayed currently * fix: "Hide Friends in Same Instance" hides players in unrelated private instances (#1210) * fix * fix: "Hide Friends in Same Instance" does not work when "Split Favorite Friends" is enabled * fix Notification.vue message * fix: deleteFavoriteNoConfirm * fix: feed status style * fix: infinite loading when deleting note * fix: private players will not be hidden when 'Hide Friends in Same Instance', and 'Hide Friends in Same Instance' will not work when 'Split Favorite Friends'
3180 lines
155 KiB
Vue
3180 lines
155 KiB
Vue
<template>
|
|
<safe-dialog
|
|
ref="userDialogRef"
|
|
class="x-dialog x-user-dialog"
|
|
:visible.sync="userDialog.visible"
|
|
:show-close="false"
|
|
width="770px"
|
|
top="10vh">
|
|
<div v-loading="userDialog.loading">
|
|
<div style="display: flex">
|
|
<el-popover
|
|
v-if="userDialog.ref.profilePicOverrideThumbnail || userDialog.ref.profilePicOverride"
|
|
placement="right"
|
|
width="500px"
|
|
trigger="click">
|
|
<img
|
|
slot="reference"
|
|
class="x-link"
|
|
:src="userDialog.ref.profilePicOverrideThumbnail || userDialog.ref.profilePicOverride"
|
|
style="flex: none; height: 120px; width: 213.33px; border-radius: 12px; object-fit: cover" />
|
|
<img
|
|
v-lazy="userDialog.ref.profilePicOverride"
|
|
class="x-link"
|
|
style="height: 400px"
|
|
@click="showFullscreenImageDialog(userDialog.ref.profilePicOverride)" />
|
|
</el-popover>
|
|
<el-popover v-else placement="right" width="500px" trigger="click">
|
|
<img
|
|
slot="reference"
|
|
class="x-link"
|
|
:src="userDialog.ref.currentAvatarThumbnailImageUrl"
|
|
style="flex: none; height: 120px; width: 160px; border-radius: 12px; object-fit: cover" />
|
|
<img
|
|
v-lazy="userDialog.ref.currentAvatarImageUrl"
|
|
class="x-link"
|
|
style="height: 500px"
|
|
@click="showFullscreenImageDialog(userDialog.ref.currentAvatarImageUrl)" />
|
|
</el-popover>
|
|
|
|
<div style="flex: 1; display: flex; align-items: center; margin-left: 15px">
|
|
<div style="flex: 1">
|
|
<div>
|
|
<el-tooltip v-if="userDialog.ref.status" placement="top">
|
|
<template #content>
|
|
<span v-if="userDialog.ref.state === 'active'">{{
|
|
t('dialog.user.status.active')
|
|
}}</span>
|
|
<span v-else-if="userDialog.ref.state === 'offline'">{{
|
|
t('dialog.user.status.offline')
|
|
}}</span>
|
|
<span v-else-if="userDialog.ref.status === 'active'">{{
|
|
t('dialog.user.status.online')
|
|
}}</span>
|
|
<span v-else-if="userDialog.ref.status === 'join me'">{{
|
|
t('dialog.user.status.join_me')
|
|
}}</span>
|
|
<span v-else-if="userDialog.ref.status === 'ask me'">{{
|
|
t('dialog.user.status.ask_me')
|
|
}}</span>
|
|
<span v-else-if="userDialog.ref.status === 'busy'">{{
|
|
t('dialog.user.status.busy')
|
|
}}</span>
|
|
<span v-else>{{ t('dialog.user.status.offline') }}</span>
|
|
</template>
|
|
<i class="x-user-status" :class="userStatusClass(userDialog.ref)"></i>
|
|
</el-tooltip>
|
|
<template v-if="userDialog.previousDisplayNames.length > 0">
|
|
<el-tooltip placement="bottom">
|
|
<template #content>
|
|
<span>{{ t('dialog.user.previous_display_names') }}</span>
|
|
<div
|
|
v-for="displayName in userDialog.previousDisplayNames"
|
|
:key="displayName"
|
|
placement="top">
|
|
<span v-text="displayName"></span>
|
|
</div>
|
|
</template>
|
|
<i class="el-icon-caret-bottom"></i>
|
|
</el-tooltip>
|
|
</template>
|
|
<el-popover placement="top" trigger="click">
|
|
<span
|
|
slot="reference"
|
|
class="dialog-title"
|
|
style="margin-left: 5px; margin-right: 5px; cursor: pointer"
|
|
v-text="userDialog.ref.displayName"></span>
|
|
<span style="display: block; text-align: center; font-family: monospace">{{
|
|
userDialog.ref.displayName | textToHex
|
|
}}</span>
|
|
</el-popover>
|
|
<el-tooltip
|
|
v-if="userDialog.ref.pronouns"
|
|
placement="top"
|
|
:content="t('dialog.user.pronouns')"
|
|
:disabled="hideTooltips">
|
|
<span
|
|
class="x-grey"
|
|
style="margin-right: 5px; font-family: monospace; font-size: 12px"
|
|
v-text="userDialog.ref.pronouns"></span>
|
|
</el-tooltip>
|
|
<el-tooltip v-for="item in userDialog.ref.$languages" :key="item.key" placement="top">
|
|
<template #content>
|
|
<span>{{ item.value }} ({{ item.key }})</span>
|
|
</template>
|
|
<span
|
|
class="flags"
|
|
:class="languageClass(item.key)"
|
|
style="display: inline-block; margin-right: 5px"></span>
|
|
</el-tooltip>
|
|
<template v-if="userDialog.ref.id === API.currentUser.id">
|
|
<br />
|
|
<el-popover placement="top" trigger="click">
|
|
<span
|
|
slot="reference"
|
|
class="x-grey"
|
|
style="
|
|
margin-right: 10px;
|
|
font-family: monospace;
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
"
|
|
v-text="API.currentUser.username"></span>
|
|
<span style="display: block; text-align: center; font-family: monospace">{{
|
|
API.currentUser.username | textToHex
|
|
}}</span>
|
|
</el-popover>
|
|
</template>
|
|
</div>
|
|
<div style="margin-top: 5px">
|
|
<el-tag
|
|
type="info"
|
|
effect="plain"
|
|
size="mini"
|
|
class="name"
|
|
:class="userDialog.ref.$trustClass"
|
|
style="margin-right: 5px; margin-top: 5px"
|
|
v-text="userDialog.ref.$trustLevel"></el-tag>
|
|
<el-tag
|
|
v-if="userDialog.isFriend && userDialog.friend"
|
|
type="info"
|
|
effect="plain"
|
|
size="mini"
|
|
class="x-tag-friend"
|
|
style="margin-right: 5px; margin-top: 5px">
|
|
{{
|
|
t('dialog.user.tags.friend_no', {
|
|
number: userDialog.ref.$friendNumber ? userDialog.ref.$friendNumber : ''
|
|
})
|
|
}}
|
|
</el-tag>
|
|
<el-tag
|
|
v-if="userDialog.ref.$isTroll"
|
|
type="info"
|
|
effect="plain"
|
|
size="mini"
|
|
class="x-tag-troll"
|
|
style="margin-right: 5px; margin-top: 5px">
|
|
Nuisance
|
|
</el-tag>
|
|
<el-tag
|
|
v-if="userDialog.ref.$isProbableTroll"
|
|
type="info"
|
|
effect="plain"
|
|
size="mini"
|
|
class="x-tag-troll"
|
|
style="margin-right: 5px; margin-top: 5px">
|
|
Almost Nuisance
|
|
</el-tag>
|
|
<el-tag
|
|
v-if="userDialog.ref.$isModerator"
|
|
type="info"
|
|
effect="plain"
|
|
size="mini"
|
|
class="x-tag-vip"
|
|
style="margin-right: 5px; margin-top: 5px">
|
|
{{ t('dialog.user.tags.vrchat_team') }}
|
|
</el-tag>
|
|
<el-tag
|
|
v-if="userDialog.ref.last_platform === 'standalonewindows'"
|
|
type="info"
|
|
effect="plain"
|
|
size="mini"
|
|
class="x-tag-platform-pc"
|
|
style="margin-right: 5px; margin-top: 5px">
|
|
PC
|
|
</el-tag>
|
|
<el-tag
|
|
v-else-if="userDialog.ref.last_platform === 'android'"
|
|
type="info"
|
|
effect="plain"
|
|
size="mini"
|
|
class="x-tag-platform-quest"
|
|
style="margin-right: 5px; margin-top: 5px">
|
|
Android
|
|
</el-tag>
|
|
<el-tag
|
|
v-else-if="userDialog.ref.last_platform === 'ios'"
|
|
type="info"
|
|
effect="plain"
|
|
size="mini"
|
|
class="x-tag-platform-ios"
|
|
style="margin-right: 5px; margin-top: 5px"
|
|
>iOS</el-tag
|
|
>
|
|
<el-tag
|
|
v-else-if="userDialog.ref.last_platform"
|
|
type="info"
|
|
effect="plain"
|
|
size="mini"
|
|
class="x-tag-platform-other"
|
|
style="margin-right: 5px; margin-top: 5px">
|
|
{{ userDialog.ref.last_platform }}
|
|
</el-tag>
|
|
<el-tag
|
|
v-if="userDialog.ref.ageVerified || userDialog.ref.ageVerificationStatus !== 'hidden'"
|
|
type="info"
|
|
effect="plain"
|
|
size="mini"
|
|
class="x-tag-age-verification"
|
|
style="margin-right: 5px; margin-top: 5px">
|
|
{{ userDialog.ref.ageVerificationStatus }}
|
|
</el-tag>
|
|
<el-tag
|
|
v-if="userDialog.ref.$customTag"
|
|
type="info"
|
|
effect="plain"
|
|
size="mini"
|
|
class="name"
|
|
:style="{
|
|
color: userDialog.ref.$customTagColour,
|
|
'border-color': userDialog.ref.$customTagColour
|
|
}"
|
|
style="margin-right: 5px; margin-top: 5px"
|
|
v-text="userDialog.ref.$customTag"></el-tag>
|
|
<br />
|
|
<template v-for="badge in userDialog.ref.badges">
|
|
<el-tooltip :key="badge.badgeId" placement="top">
|
|
<template #content>
|
|
<span>{{ badge.badgeName }}</span>
|
|
<span v-if="badge.hidden"> (Hidden)</span>
|
|
</template>
|
|
<el-popover placement="right" width="300px" trigger="click">
|
|
<img
|
|
slot="reference"
|
|
class="x-link x-user-badge"
|
|
:src="badge.badgeImageUrl"
|
|
style="
|
|
flex: none;
|
|
height: 32px;
|
|
width: 32px;
|
|
border-radius: 3px;
|
|
object-fit: cover;
|
|
margin-top: 5px;
|
|
margin-right: 5px;
|
|
"
|
|
:class="{ 'x-user-badge-hidden': badge.hidden }" />
|
|
<img
|
|
v-lazy="badge.badgeImageUrl"
|
|
class="x-link"
|
|
style="width: 300px"
|
|
@click="showFullscreenImageDialog(badge.badgeImageUrl)" />
|
|
<br />
|
|
<div style="display: block; width: 300px; word-break: normal">
|
|
<span>{{ badge.badgeName }}</span>
|
|
<br />
|
|
<span class="x-grey" style="font-size: 12px">{{
|
|
badge.badgeDescription
|
|
}}</span>
|
|
<br />
|
|
<span
|
|
v-if="badge.assignedAt"
|
|
class="x-grey"
|
|
style="font-family: monospace; font-size: 12px">
|
|
{{ t('dialog.user.badges.assigned') }}:
|
|
{{ badge.assignedAt | formatDate('long') }}
|
|
</span>
|
|
<template v-if="userDialog.id === API.currentUser.id">
|
|
<br />
|
|
<el-checkbox
|
|
v-model="badge.hidden"
|
|
style="margin-top: 5px"
|
|
@change="toggleBadgeVisibility(badge)">
|
|
{{ t('dialog.user.badges.hidden') }}
|
|
</el-checkbox>
|
|
<br />
|
|
<el-checkbox
|
|
v-model="badge.showcased"
|
|
style="margin-top: 5px"
|
|
@change="toggleBadgeShowcased(badge)">
|
|
{{ t('dialog.user.badges.showcased') }}
|
|
</el-checkbox>
|
|
</template>
|
|
</div>
|
|
</el-popover>
|
|
</el-tooltip>
|
|
</template>
|
|
</div>
|
|
<div style="margin-top: 5px">
|
|
<span style="font-size: 12px" v-text="userDialog.ref.statusDescription"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="userDialog.ref.userIcon" style="flex: none; margin-right: 10px">
|
|
<el-popover placement="right" width="500px" trigger="click">
|
|
<img
|
|
slot="reference"
|
|
class="x-link"
|
|
:src="userImage(userDialog.ref, true, '256', true)"
|
|
style="
|
|
flex: none;
|
|
width: 120px;
|
|
height: 120px;
|
|
border-radius: 12px;
|
|
object-fit: cover;
|
|
" />
|
|
<img
|
|
v-lazy="userDialog.ref.userIcon"
|
|
class="x-link"
|
|
style="height: 500px"
|
|
@click="showFullscreenImageDialog(userDialog.ref.userIcon)" />
|
|
</el-popover>
|
|
</div>
|
|
|
|
<div style="flex: none">
|
|
<template
|
|
v-if="
|
|
(API.currentUser.id !== userDialog.ref.id && userDialog.isFriend) ||
|
|
userDialog.isFavorite
|
|
">
|
|
<el-tooltip
|
|
v-if="userDialog.isFavorite"
|
|
placement="top"
|
|
:content="t('dialog.user.actions.unfavorite_tooltip')"
|
|
:disabled="hideTooltips">
|
|
<el-button
|
|
type="warning"
|
|
icon="el-icon-star-on"
|
|
circle
|
|
@click="userDialogCommand('Add Favorite')"></el-button>
|
|
</el-tooltip>
|
|
<el-tooltip
|
|
v-else
|
|
placement="top"
|
|
:content="t('dialog.user.actions.favorite_tooltip')"
|
|
:disabled="hideTooltips">
|
|
<el-button
|
|
type="default"
|
|
icon="el-icon-star-off"
|
|
circle
|
|
@click="userDialogCommand('Add Favorite')"></el-button>
|
|
</el-tooltip>
|
|
</template>
|
|
<el-dropdown trigger="click" size="small" @command="userDialogCommand">
|
|
<el-button
|
|
:type="
|
|
userDialog.incomingRequest || userDialog.outgoingRequest
|
|
? 'success'
|
|
: userDialog.isBlock || userDialog.isMute
|
|
? 'danger'
|
|
: 'default'
|
|
"
|
|
icon="el-icon-more"
|
|
circle
|
|
style="margin-left: 5px"></el-button>
|
|
<el-dropdown-menu slot="dropdown">
|
|
<el-dropdown-item icon="el-icon-refresh" command="Refresh">{{
|
|
t('dialog.user.actions.refresh')
|
|
}}</el-dropdown-item>
|
|
<el-dropdown-item icon="el-icon-share" command="Share">{{
|
|
t('dialog.user.actions.share')
|
|
}}</el-dropdown-item>
|
|
<template v-if="userDialog.ref.id === API.currentUser.id">
|
|
<el-dropdown-item icon="el-icon-picture-outline" command="Manage Gallery" divided>{{
|
|
t('dialog.user.actions.manage_gallery_icon')
|
|
}}</el-dropdown-item>
|
|
<el-dropdown-item icon="el-icon-s-custom" command="Show Avatar Author">{{
|
|
t('dialog.user.actions.show_avatar_author')
|
|
}}</el-dropdown-item>
|
|
<el-dropdown-item icon="el-icon-s-custom" command="Show Fallback Avatar Details">{{
|
|
t('dialog.user.actions.show_fallback_avatar')
|
|
}}</el-dropdown-item>
|
|
<el-dropdown-item icon="el-icon-edit" command="Edit Social Status" divided>{{
|
|
t('dialog.user.actions.edit_status')
|
|
}}</el-dropdown-item>
|
|
<el-dropdown-item icon="el-icon-edit" command="Edit Language">{{
|
|
t('dialog.user.actions.edit_language')
|
|
}}</el-dropdown-item>
|
|
<el-dropdown-item icon="el-icon-edit" command="Edit Bio">{{
|
|
t('dialog.user.actions.edit_bio')
|
|
}}</el-dropdown-item>
|
|
<el-dropdown-item icon="el-icon-edit" command="Edit Pronouns">{{
|
|
t('dialog.user.actions.edit_pronouns')
|
|
}}</el-dropdown-item>
|
|
<el-dropdown-item icon="el-icon-switch-button" command="Logout" divided>{{
|
|
t('dialog.user.actions.logout')
|
|
}}</el-dropdown-item>
|
|
</template>
|
|
<template v-else>
|
|
<template v-if="userDialog.isFriend">
|
|
<el-dropdown-item icon="el-icon-postcard" command="Request Invite" divided>{{
|
|
t('dialog.user.actions.request_invite')
|
|
}}</el-dropdown-item>
|
|
<el-dropdown-item icon="el-icon-postcard" command="Request Invite Message">{{
|
|
t('dialog.user.actions.request_invite_with_message')
|
|
}}</el-dropdown-item>
|
|
<template
|
|
v-if="
|
|
lastLocation.location &&
|
|
isGameRunning &&
|
|
checkCanInvite(lastLocation.location)
|
|
">
|
|
<el-dropdown-item icon="el-icon-message" command="Invite">{{
|
|
t('dialog.user.actions.invite')
|
|
}}</el-dropdown-item>
|
|
<el-dropdown-item icon="el-icon-message" command="Invite Message">{{
|
|
t('dialog.user.actions.invite_with_message')
|
|
}}</el-dropdown-item>
|
|
</template>
|
|
</template>
|
|
<template v-else-if="userDialog.incomingRequest">
|
|
<el-dropdown-item icon="el-icon-check" command="Accept Friend Request">{{
|
|
t('dialog.user.actions.accept_friend_request')
|
|
}}</el-dropdown-item>
|
|
<el-dropdown-item icon="el-icon-close" command="Decline Friend Request">{{
|
|
t('dialog.user.actions.decline_friend_request')
|
|
}}</el-dropdown-item>
|
|
</template>
|
|
<el-dropdown-item
|
|
v-else-if="userDialog.outgoingRequest"
|
|
icon="el-icon-close"
|
|
command="Cancel Friend Request">
|
|
{{ t('dialog.user.actions.cancel_friend_request') }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item v-else icon="el-icon-plus" command="Send Friend Request">{{
|
|
t('dialog.user.actions.send_friend_request')
|
|
}}</el-dropdown-item>
|
|
<el-dropdown-item icon="el-icon-message" command="Invite To Group">{{
|
|
t('dialog.user.actions.invite_to_group')
|
|
}}</el-dropdown-item>
|
|
<!--//- el-dropdown-item(icon="el-icon-thumb" command="Send Boop" :disabled="!API.currentUser.isBoopingEnabled") {{ t('dialog.user.actions.send_boop') }}-->
|
|
<el-dropdown-item icon="el-icon-s-custom" command="Show Avatar Author" divided>{{
|
|
t('dialog.user.actions.show_avatar_author')
|
|
}}</el-dropdown-item>
|
|
<el-dropdown-item icon="el-icon-s-custom" command="Show Fallback Avatar Details">{{
|
|
t('dialog.user.actions.show_fallback_avatar')
|
|
}}</el-dropdown-item>
|
|
<el-dropdown-item icon="el-icon-tickets" command="Previous Instances">{{
|
|
t('dialog.user.actions.show_previous_instances')
|
|
}}</el-dropdown-item>
|
|
<el-dropdown-item
|
|
v-if="userDialog.ref.currentAvatarImageUrl"
|
|
icon="el-icon-picture-outline"
|
|
command="Previous Images">
|
|
{{ t('dialog.user.actions.show_previous_images') }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item
|
|
v-if="userDialog.isBlock"
|
|
icon="el-icon-circle-check"
|
|
command="Moderation Unblock"
|
|
divided
|
|
style="color: #f56c6c">
|
|
{{ t('dialog.user.actions.moderation_unblock') }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item
|
|
v-else
|
|
icon="el-icon-circle-close"
|
|
command="Moderation Block"
|
|
divided
|
|
:disabled="userDialog.ref.$isModerator">
|
|
{{ t('dialog.user.actions.moderation_block') }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item
|
|
v-if="userDialog.isMute"
|
|
icon="el-icon-microphone"
|
|
command="Moderation Unmute"
|
|
style="color: #f56c6c">
|
|
{{ t('dialog.user.actions.moderation_unmute') }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item
|
|
v-else
|
|
icon="el-icon-turn-off-microphone"
|
|
command="Moderation Mute"
|
|
:disabled="userDialog.ref.$isModerator">
|
|
{{ t('dialog.user.actions.moderation_mute') }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item
|
|
v-if="userDialog.isMuteChat"
|
|
icon="el-icon-chat-line-round"
|
|
command="Moderation Enable Chatbox"
|
|
style="color: #f56c6c">
|
|
{{ t('dialog.user.actions.moderation_enable_chatbox') }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item
|
|
v-else
|
|
icon="el-icon-chat-dot-round"
|
|
command="Moderation Disable Chatbox">
|
|
{{ t('dialog.user.actions.moderation_disable_chatbox') }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item icon="el-icon-user-solid" command="Show Avatar">
|
|
<i v-if="userDialog.isShowAvatar" class="el-icon-check el-icon--left"></i>
|
|
<span>{{ t('dialog.user.actions.moderation_show_avatar') }}</span>
|
|
</el-dropdown-item>
|
|
<el-dropdown-item icon="el-icon-user" command="Hide Avatar">
|
|
<i v-if="userDialog.isHideAvatar" class="el-icon-check el-icon--left"></i>
|
|
<span>{{ t('dialog.user.actions.moderation_hide_avatar') }}</span>
|
|
</el-dropdown-item>
|
|
<el-dropdown-item
|
|
v-if="userDialog.isInteractOff"
|
|
icon="el-icon-thumb"
|
|
command="Moderation Enable Avatar Interaction"
|
|
style="color: #f56c6c">
|
|
{{ t('dialog.user.actions.moderation_enable_avatar_interaction') }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item
|
|
v-else
|
|
icon="el-icon-circle-close"
|
|
command="Moderation Disable Avatar Interaction">
|
|
{{ t('dialog.user.actions.moderation_disable_avatar_interaction') }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item
|
|
icon="el-icon-s-flag"
|
|
command="Report Hacking"
|
|
:disabled="userDialog.ref.$isModerator">
|
|
{{ t('dialog.user.actions.report_hacking') }}
|
|
</el-dropdown-item>
|
|
<template v-if="userDialog.isFriend">
|
|
<el-dropdown-item
|
|
icon="el-icon-delete"
|
|
command="Unfriend"
|
|
divided
|
|
style="color: #f56c6c">
|
|
{{ t('dialog.user.actions.unfriend') }}
|
|
</el-dropdown-item>
|
|
</template>
|
|
</template>
|
|
</el-dropdown-menu>
|
|
</el-dropdown>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<el-tabs ref="userDialogTabsRef" @tab-click="userDialogTabClick">
|
|
<el-tab-pane :label="t('dialog.user.info.header')">
|
|
<template v-if="isFriendOnline(userDialog.friend) || API.currentUser.id === userDialog.id">
|
|
<div
|
|
v-if="userDialog.ref.location"
|
|
style="
|
|
display: flex;
|
|
flex-direction: column;
|
|
margin-bottom: 10px;
|
|
padding-bottom: 10px;
|
|
border-bottom: 1px solid #e4e7ed14;
|
|
">
|
|
<div style="flex: none">
|
|
<template v-if="isRealInstance(userDialog.$location.tag)">
|
|
<launch
|
|
:location="userDialog.$location.tag"
|
|
@show-launch-dialog="showLaunchDialog"></launch>
|
|
<el-tooltip
|
|
placement="top"
|
|
:content="t('dialog.user.info.self_invite_tooltip')"
|
|
:disabled="hideTooltips">
|
|
<invite-yourself
|
|
:location="userDialog.$location.tag"
|
|
:shortname="userDialog.$location.shortName"
|
|
style="margin-left: 5px"></invite-yourself>
|
|
</el-tooltip>
|
|
<el-tooltip
|
|
placement="top"
|
|
:content="t('dialog.user.info.refresh_instance_info')"
|
|
:disabled="hideTooltips">
|
|
<el-button
|
|
size="mini"
|
|
icon="el-icon-refresh"
|
|
style="margin-left: 5px"
|
|
circle
|
|
@click="refreshInstancePlayerCount(userDialog.$location.tag)"></el-button>
|
|
</el-tooltip>
|
|
<last-join
|
|
:location="userDialog.$location.tag"
|
|
:currentlocation="lastLocation.location"></last-join>
|
|
<instance-info
|
|
:location="userDialog.$location.tag"
|
|
:instance="userDialog.instance.ref"
|
|
:friendcount="userDialog.instance.friendCount"
|
|
:updateelement="updateInstanceInfo"></instance-info>
|
|
</template>
|
|
<location
|
|
:location="userDialog.ref.location"
|
|
:traveling="userDialog.ref.travelingToLocation"
|
|
style="display: block; margin-top: 5px"></location>
|
|
</div>
|
|
<div class="x-friend-list" style="flex: 1; margin-top: 10px; max-height: 150px">
|
|
<div
|
|
v-if="userDialog.$location.userId"
|
|
class="x-friend-item x-friend-item-border"
|
|
@click="showUserDialog(userDialog.$location.userId)">
|
|
<template v-if="userDialog.$location.user">
|
|
<div class="avatar" :class="userStatusClass(userDialog.$location.user)">
|
|
<img :src="userImage(userDialog.$location.user, true)" />
|
|
</div>
|
|
<div class="detail">
|
|
<span
|
|
class="name"
|
|
:style="{ color: userDialog.$location.user.$userColour }"
|
|
v-text="userDialog.$location.user.displayName"></span>
|
|
<span class="extra">{{ t('dialog.user.info.instance_creator') }}</span>
|
|
</div>
|
|
</template>
|
|
<span v-else v-text="userDialog.$location.userId"></span>
|
|
</div>
|
|
<div
|
|
v-for="user in userDialog.users"
|
|
:key="user.id"
|
|
class="x-friend-item x-friend-item-border"
|
|
@click="showUserDialog(user.id)">
|
|
<div class="avatar" :class="userStatusClass(user)">
|
|
<img :src="userImage(user, true)" />
|
|
</div>
|
|
<div class="detail">
|
|
<span
|
|
class="name"
|
|
:style="{ color: user.$userColour }"
|
|
v-text="user.displayName"></span>
|
|
<span v-if="user.location === 'traveling'" class="extra">
|
|
<i class="el-icon-loading" style="margin-right: 5px"></i>
|
|
<timer :epoch="user.$travelingToTime"></timer>
|
|
</span>
|
|
<span v-else class="extra">
|
|
<timer :epoch="user.$location_at"></timer>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<div class="x-friend-list" style="max-height: none">
|
|
<div v-if="!hideUserNotes" class="x-friend-item" style="width: 100%; cursor: default">
|
|
<div class="detail">
|
|
<span class="name">{{ t('dialog.user.info.note') }}</span>
|
|
<el-input
|
|
v-model="userDialog.note"
|
|
type="textarea"
|
|
maxlength="256"
|
|
show-word-limit
|
|
:rows="2"
|
|
:autosize="{ minRows: 1, maxRows: 20 }"
|
|
:placeholder="t('dialog.user.info.note_placeholder')"
|
|
size="mini"
|
|
resize="none"
|
|
@change="checkNote(userDialog.ref, userDialog.note)"
|
|
@input="cleanNote(userDialog.note)"></el-input>
|
|
<div style="float: right">
|
|
<i
|
|
v-if="userDialog.noteSaving"
|
|
class="el-icon-loading"
|
|
style="margin-left: 5px"></i>
|
|
<i
|
|
v-else-if="userDialog.note !== userDialog.ref.note"
|
|
class="el-icon-more-outline"
|
|
style="margin-left: 5px"></i>
|
|
<el-button
|
|
v-if="userDialog.note"
|
|
type="text"
|
|
icon="el-icon-delete"
|
|
size="mini"
|
|
style="margin-left: 5px"
|
|
@click="deleteNote(userDialog.id)"></el-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-if="!hideUserMemos" class="x-friend-item" style="width: 100%; cursor: default">
|
|
<div class="detail">
|
|
<span class="name">{{ t('dialog.user.info.memo') }}</span>
|
|
<el-input
|
|
v-model="userDialog.memo"
|
|
class="extra"
|
|
type="textarea"
|
|
:rows="2"
|
|
:autosize="{ minRows: 1, maxRows: 20 }"
|
|
:placeholder="t('dialog.user.info.memo_placeholder')"
|
|
size="mini"
|
|
resize="none"
|
|
@change="onUserMemoChange"></el-input>
|
|
</div>
|
|
</div>
|
|
<div class="x-friend-item" style="width: 100%; cursor: default">
|
|
<div class="detail">
|
|
<span
|
|
v-if="
|
|
userDialog.id !== API.currentUser.id &&
|
|
userDialog.ref.profilePicOverride &&
|
|
userDialog.ref.currentAvatarImageUrl
|
|
"
|
|
class="name">
|
|
{{ t('dialog.user.info.avatar_info_last_seen') }}
|
|
</span>
|
|
<span v-else class="name">{{ t('dialog.user.info.avatar_info') }}</span>
|
|
<div class="extra">
|
|
<avatar-info
|
|
:imageurl="userDialog.ref.currentAvatarImageUrl"
|
|
:userid="userDialog.id"
|
|
:avatartags="userDialog.ref.currentAvatarTags"
|
|
style="display: inline-block"></avatar-info>
|
|
<el-tooltip
|
|
v-if="
|
|
userDialog.ref.profilePicOverride &&
|
|
!userDialog.ref.currentAvatarImageUrl &&
|
|
!hideTooltips
|
|
"
|
|
placement="top"
|
|
:content="t('dialog.user.info.vrcplus_hides_avatar')">
|
|
<i class="el-icon-warning"></i>
|
|
</el-tooltip>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="x-friend-item" style="width: 100%; cursor: default">
|
|
<div class="detail">
|
|
<span class="name" style="margin-bottom: 5px">{{
|
|
t('dialog.user.info.represented_group')
|
|
}}</span>
|
|
<div
|
|
v-if="
|
|
userDialog.isRepresentedGroupLoading ||
|
|
(userDialog.representedGroup && userDialog.representedGroup.isRepresenting)
|
|
"
|
|
class="extra">
|
|
<div style="display: inline-block; flex: none; margin-right: 5px">
|
|
<el-popover placement="right" width="500px" trigger="click">
|
|
<el-image
|
|
slot="reference"
|
|
v-loading="userDialog.isRepresentedGroupLoading"
|
|
class="x-link"
|
|
:src="userDialog.representedGroup.$thumbnailUrl"
|
|
style="
|
|
flex: none;
|
|
width: 60px;
|
|
height: 60px;
|
|
border-radius: 4px;
|
|
object-fit: cover;
|
|
"
|
|
:style="{
|
|
background: userDialog.isRepresentedGroupLoading ? '#f5f7fa' : ''
|
|
}"
|
|
@load="userDialog.isRepresentedGroupLoading = false">
|
|
<div slot="error"></div>
|
|
</el-image>
|
|
<img
|
|
v-lazy="userDialog.representedGroup.iconUrl"
|
|
class="x-link"
|
|
style="height: 500px"
|
|
@click="
|
|
showFullscreenImageDialog(userDialog.representedGroup.iconUrl)
|
|
" />
|
|
</el-popover>
|
|
</div>
|
|
<span
|
|
v-if="userDialog.representedGroup.isRepresenting"
|
|
style="vertical-align: top; cursor: pointer"
|
|
@click="showGroupDialog(userDialog.representedGroup.groupId)">
|
|
<span
|
|
v-if="userDialog.representedGroup.ownerId === userDialog.id"
|
|
style="margin-right: 5px"
|
|
>👑</span
|
|
>
|
|
<span
|
|
style="margin-right: 5px"
|
|
v-text="userDialog.representedGroup.name"></span>
|
|
<span>({{ userDialog.representedGroup.memberCount }})</span>
|
|
</span>
|
|
</div>
|
|
<div v-else class="extra">-</div>
|
|
</div>
|
|
</div>
|
|
<div class="x-friend-item" style="width: 100%; cursor: default">
|
|
<div class="detail">
|
|
<span class="name">{{ t('dialog.user.info.bio') }}</span>
|
|
<pre
|
|
class="extra"
|
|
style="
|
|
font-family: inherit;
|
|
font-size: 12px;
|
|
white-space: pre-wrap;
|
|
margin: 0 0.5em 0 0;
|
|
max-height: 40vh;
|
|
overflow-y: auto;
|
|
"
|
|
>{{ userDialog.ref.bio || '-' }}</pre
|
|
>
|
|
<div v-if="userDialog.id === API.currentUser.id" style="float: right">
|
|
<el-button
|
|
type="text"
|
|
icon="el-icon-edit"
|
|
size="mini"
|
|
style="margin-left: 5px"
|
|
@click="showBioDialog"></el-button>
|
|
</div>
|
|
<div style="margin-top: 5px">
|
|
<el-tooltip v-for="(link, index) in userDialog.ref.bioLinks" :key="index">
|
|
<template #content>
|
|
<span v-text="link"></span>
|
|
</template>
|
|
<img
|
|
:src="getFaviconUrl(link)"
|
|
onerror="this.onerror=null;this.class='el-icon-error'"
|
|
style="
|
|
width: 16px;
|
|
height: 16px;
|
|
vertical-align: middle;
|
|
margin-right: 5px;
|
|
cursor: pointer;
|
|
"
|
|
@click.stop="openExternalLink(link)" />
|
|
</el-tooltip>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<template v-if="API.currentUser.id !== userDialog.id">
|
|
<div class="x-friend-item" style="cursor: default">
|
|
<div class="detail">
|
|
<span class="name">
|
|
{{ t('dialog.user.info.last_seen') }}
|
|
<el-tooltip
|
|
v-if="!hideTooltips"
|
|
placement="top"
|
|
style="margin-left: 5px"
|
|
:content="t('dialog.user.info.accuracy_notice')">
|
|
<i class="el-icon-warning"></i>
|
|
</el-tooltip>
|
|
</span>
|
|
<span class="extra">{{ userDialog.lastSeen | formatDate('long') }}</span>
|
|
</div>
|
|
</div>
|
|
<el-tooltip
|
|
:disabled="hideTooltips"
|
|
placement="top"
|
|
:content="t('dialog.user.info.open_previouse_instance')">
|
|
<div class="x-friend-item" @click="showPreviousInstancesUserDialog(userDialog.ref)">
|
|
<div class="detail">
|
|
<span class="name">
|
|
{{ t('dialog.user.info.join_count') }}
|
|
<el-tooltip
|
|
v-if="!hideTooltips"
|
|
placement="top"
|
|
style="margin-left: 5px"
|
|
:content="t('dialog.user.info.accuracy_notice')">
|
|
<i class="el-icon-warning"></i>
|
|
</el-tooltip>
|
|
</span>
|
|
<span v-if="userDialog.joinCount === 0" class="extra">-</span>
|
|
<span v-else class="extra" v-text="userDialog.joinCount"></span>
|
|
</div>
|
|
</div>
|
|
</el-tooltip>
|
|
<div class="x-friend-item" style="cursor: default">
|
|
<div class="detail">
|
|
<span class="name">
|
|
{{ t('dialog.user.info.time_together') }}
|
|
<el-tooltip
|
|
v-if="!hideTooltips"
|
|
placement="top"
|
|
style="margin-left: 5px"
|
|
:content="t('dialog.user.info.accuracy_notice')">
|
|
<i class="el-icon-warning"></i>
|
|
</el-tooltip>
|
|
</span>
|
|
<span v-if="userDialog.timeSpent === 0" class="extra">-</span>
|
|
<span v-else class="extra">{{ timeToText(userDialog.timeSpent) }}</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
<el-tooltip
|
|
:disabled="hideTooltips || API.currentUser.id !== userDialog.id"
|
|
placement="top"
|
|
:content="t('dialog.user.info.open_previouse_instance')">
|
|
<div class="x-friend-item" @click="showPreviousInstancesUserDialog(userDialog.ref)">
|
|
<div class="detail">
|
|
<span class="name">
|
|
{{ t('dialog.user.info.play_time') }}
|
|
<el-tooltip
|
|
v-if="!hideTooltips"
|
|
placement="top"
|
|
style="margin-left: 5px"
|
|
:content="t('dialog.user.info.accuracy_notice')">
|
|
<i class="el-icon-warning"></i>
|
|
</el-tooltip>
|
|
</span>
|
|
<span v-if="userDialog.timeSpent === 0" class="extra">-</span>
|
|
<span v-else class="extra">{{ timeToText(userDialog.timeSpent) }}</span>
|
|
</div>
|
|
</div>
|
|
</el-tooltip>
|
|
</template>
|
|
<div class="x-friend-item" style="cursor: default">
|
|
<el-tooltip :placement="API.currentUser.id !== userDialog.id ? 'bottom' : 'top'">
|
|
<template #content>
|
|
<span>{{ userOnlineForTimestamp(userDialog) | formatDate('short') }}</span>
|
|
</template>
|
|
<div class="detail">
|
|
<span
|
|
v-if="userDialog.ref.state === 'online' && userDialog.ref.$online_for"
|
|
class="name">
|
|
{{ t('dialog.user.info.online_for') }}
|
|
<el-tooltip
|
|
v-if="!hideTooltips"
|
|
placement="top"
|
|
style="margin-left: 5px"
|
|
:content="t('dialog.user.info.accuracy_notice')">
|
|
<i class="el-icon-warning"></i>
|
|
</el-tooltip>
|
|
</span>
|
|
<span v-else class="name">
|
|
{{ t('dialog.user.info.offline_for') }}
|
|
<el-tooltip
|
|
v-if="!hideTooltips"
|
|
placement="top"
|
|
style="margin-left: 5px"
|
|
:content="t('dialog.user.info.accuracy_notice')">
|
|
<i class="el-icon-warning"></i>
|
|
</el-tooltip>
|
|
</span>
|
|
<span class="extra">{{ userOnlineFor(userDialog) }}</span>
|
|
</div>
|
|
</el-tooltip>
|
|
</div>
|
|
<div class="x-friend-item" style="cursor: default">
|
|
<el-tooltip :placement="API.currentUser.id !== userDialog.id ? 'bottom' : 'top'">
|
|
<template #content>
|
|
<span
|
|
>{{ t('dialog.user.info.last_login') }}
|
|
{{ userDialog.ref.last_login | formatDate('short') }}</span
|
|
>
|
|
</template>
|
|
<div class="detail">
|
|
<span class="name">{{ t('dialog.user.info.last_activity') }}</span>
|
|
<span class="extra">{{ userDialog.ref.last_activity | formatDate('long') }}</span>
|
|
</div>
|
|
</el-tooltip>
|
|
</div>
|
|
<div class="x-friend-item" style="cursor: default">
|
|
<div class="detail">
|
|
<span class="name">{{ t('dialog.user.info.date_joined') }}</span>
|
|
<span class="extra" v-text="userDialog.ref.date_joined"></span>
|
|
</div>
|
|
</div>
|
|
<div v-if="API.currentUser.id !== userDialog.id" class="x-friend-item" style="cursor: default">
|
|
<el-tooltip placement="top" :disabled="!userDialog.dateFriendedInfo.length">
|
|
<template v-if="userDialog.dateFriendedInfo.length" #content>
|
|
<template v-for="ref in userDialog.dateFriendedInfo">
|
|
<span>{{ ref.type }}: {{ ref.created_at | formatDate('long') }}</span
|
|
><br />
|
|
</template>
|
|
</template>
|
|
<div class="detail">
|
|
<span v-if="userDialog.unFriended" class="name">
|
|
{{ t('dialog.user.info.unfriended') }}
|
|
<el-tooltip
|
|
v-if="!hideTooltips"
|
|
placement="top"
|
|
style="margin-left: 5px"
|
|
:content="t('dialog.user.info.accuracy_notice')">
|
|
<i class="el-icon-warning"></i>
|
|
</el-tooltip>
|
|
</span>
|
|
<span v-else class="name">
|
|
{{ t('dialog.user.info.friended') }}
|
|
<el-tooltip
|
|
v-if="!hideTooltips"
|
|
placement="top"
|
|
style="margin-left: 5px"
|
|
:content="t('dialog.user.info.accuracy_notice')">
|
|
<i class="el-icon-warning"></i>
|
|
</el-tooltip>
|
|
</span>
|
|
<span class="extra">{{ userDialog.dateFriended | formatDate('long') }}</span>
|
|
</div>
|
|
</el-tooltip>
|
|
</div>
|
|
<template v-if="API.currentUser.id === userDialog.id">
|
|
<div class="x-friend-item" @click="toggleAvatarCopying">
|
|
<div class="detail">
|
|
<span class="name">{{ t('dialog.user.info.avatar_cloning') }}</span>
|
|
<span
|
|
v-if="API.currentUser.allowAvatarCopying"
|
|
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>
|
|
<!--//- .x-friend-item(@click="toggleAllowBooping")-->
|
|
<!--//- .detail-->
|
|
<!--//- span.name {{ t('dialog.user.info.booping') }}-->
|
|
<!--//- span.extra(v-if="API.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') }}-->
|
|
</template>
|
|
<template v-else>
|
|
<div class="x-friend-item" style="cursor: default">
|
|
<div class="detail">
|
|
<span class="name">{{ t('dialog.user.info.avatar_cloning') }}</span>
|
|
<span
|
|
v-if="userDialog.ref.allowAvatarCopying"
|
|
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>
|
|
<div
|
|
v-if="userDialog.ref.id === API.currentUser.id && API.currentUser.homeLocation"
|
|
class="x-friend-item"
|
|
style="width: 100%"
|
|
@click="showWorldDialog(API.currentUser.homeLocation)">
|
|
<div class="detail">
|
|
<span class="name">{{ t('dialog.user.info.home_location') }}</span>
|
|
<span class="extra">
|
|
<span v-text="userDialog.$homeLocationName"></span>
|
|
<el-button
|
|
size="mini"
|
|
icon="el-icon-delete"
|
|
circle
|
|
style="margin-left: 5px"
|
|
@click.stop="resetHome()">
|
|
</el-button>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="x-friend-item" style="width: 100%; cursor: default">
|
|
<div class="detail">
|
|
<span class="name">{{ t('dialog.user.info.id') }}</span>
|
|
<span class="extra">
|
|
{{ userDialog.id }}
|
|
<el-tooltip
|
|
placement="top"
|
|
:content="t('dialog.user.info.id_tooltip')"
|
|
:disabled="hideTooltips">
|
|
<el-dropdown
|
|
trigger="click"
|
|
size="mini"
|
|
style="margin-left: 5px"
|
|
@click.native.stop>
|
|
<el-button
|
|
type="default"
|
|
icon="el-icon-s-order"
|
|
size="mini"
|
|
circle></el-button>
|
|
<el-dropdown-menu slot="dropdown">
|
|
<el-dropdown-item @click.native="copyUserId(userDialog.id)">{{
|
|
t('dialog.user.info.copy_id')
|
|
}}</el-dropdown-item>
|
|
<el-dropdown-item @click.native="copyUserURL(userDialog.id)">{{
|
|
t('dialog.user.info.copy_url')
|
|
}}</el-dropdown-item>
|
|
<el-dropdown-item
|
|
@click.native="copyUserDisplayName(userDialog.ref.displayName)"
|
|
>{{ t('dialog.user.info.copy_display_name') }}</el-dropdown-item
|
|
>
|
|
</el-dropdown-menu>
|
|
</el-dropdown>
|
|
</el-tooltip>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane :label="t('dialog.user.groups.header')" lazy>
|
|
<div style="display: flex; align-items: center; justify-content: space-between">
|
|
<div style="display: flex; align-items: center">
|
|
<el-button
|
|
type="default"
|
|
:loading="userDialog.isGroupsLoading"
|
|
size="mini"
|
|
icon="el-icon-refresh"
|
|
circle
|
|
@click="getUserGroups(userDialog.id)">
|
|
</el-button>
|
|
<span style="margin-left: 5px">{{
|
|
t('dialog.user.groups.total_count', { count: userGroups.groups.length })
|
|
}}</span>
|
|
<template v-if="userDialogGroupEditMode">
|
|
<span style="margin-left: 10px; color: #909399; font-size: 10px">{{
|
|
t('dialog.user.groups.hold_shift')
|
|
}}</span>
|
|
</template>
|
|
</div>
|
|
<div style="display: flex; align-items: center">
|
|
<template v-if="!userDialogGroupEditMode">
|
|
<span style="margin-right: 5px">{{ t('dialog.user.groups.sort_by') }}</span>
|
|
<el-dropdown
|
|
trigger="click"
|
|
size="small"
|
|
style="margin-right: 5px"
|
|
:disabled="userDialog.isGroupsLoading"
|
|
@click.native.stop>
|
|
<el-button size="mini">
|
|
<span
|
|
>{{ t(userDialog.groupSorting.name) }}
|
|
<i class="el-icon-arrow-down el-icon--right"></i
|
|
></span>
|
|
</el-button>
|
|
<el-dropdown-menu slot="dropdown">
|
|
<el-dropdown-item
|
|
v-for="(item, key) in userDialogGroupSortingOptions"
|
|
:key="key"
|
|
:disabled="
|
|
item === userDialogGroupSortingOptions.inGame &&
|
|
userDialog.id !== API.currentUser.id
|
|
"
|
|
@click.native="setUserDialogGroupSorting(item)"
|
|
>{{ t(item.name) }}
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</el-dropdown>
|
|
</template>
|
|
<el-button
|
|
v-if="userDialogGroupEditMode"
|
|
size="small"
|
|
icon="el-icon-edit"
|
|
style="margin-right: 5px; height: 29px; padding: 7px 15px"
|
|
@click="exitEditModeCurrentUserGroups">
|
|
{{ t('dialog.user.groups.exit_edit_mode') }}
|
|
</el-button>
|
|
<el-button
|
|
v-else-if="API.currentUser.id === userDialog.id"
|
|
size="small"
|
|
icon="el-icon-edit"
|
|
style="margin-right: 5px; height: 29px; padding: 7px 15px"
|
|
@click="editModeCurrentUserGroups">
|
|
{{ t('dialog.user.groups.edit_mode') }}
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
<div v-loading="userDialog.isGroupsLoading" style="margin-top: 10px">
|
|
<template v-if="userDialogGroupEditMode">
|
|
<div class="x-friend-list" style="margin-top: 10px; margin-bottom: 15px; max-height: unset">
|
|
<div
|
|
v-for="group in userDialogGroupEditGroups"
|
|
:key="group.id"
|
|
class="x-friend-item x-friend-item-border"
|
|
style="width: 100%"
|
|
@click="showGroupDialog(group.id)">
|
|
<div style="margin-right: 3px; margin-left: 5px" @click.stop>
|
|
<el-button
|
|
size="mini"
|
|
icon="el-icon-download"
|
|
style="
|
|
display: block;
|
|
padding: 7px;
|
|
font-size: 9px;
|
|
margin-left: 0;
|
|
rotate: 180deg;
|
|
"
|
|
@click="moveGroupTop(group.id)">
|
|
</el-button>
|
|
<el-button
|
|
size="mini"
|
|
icon="el-icon-download"
|
|
style="display: block; padding: 7px; font-size: 9px; margin-left: 0"
|
|
@click="moveGroupBottom(group.id)">
|
|
</el-button>
|
|
</div>
|
|
<div style="margin-right: 10px" @click.stop>
|
|
<el-button
|
|
size="mini"
|
|
icon="el-icon-top"
|
|
style="display: block; padding: 7px; font-size: 9px; margin-left: 0"
|
|
@click="moveGroupUp(group.id)">
|
|
</el-button>
|
|
<el-button
|
|
size="mini"
|
|
icon="el-icon-bottom"
|
|
style="display: block; padding: 7px; font-size: 9px; margin-left: 0"
|
|
@click="moveGroupDown(group.id)">
|
|
</el-button>
|
|
</div>
|
|
<div class="avatar">
|
|
<img v-lazy="group.iconUrl" />
|
|
</div>
|
|
<div class="detail">
|
|
<span class="name" v-text="group.name"></span>
|
|
<span class="extra">
|
|
<el-tooltip
|
|
v-if="group.isRepresenting"
|
|
placement="top"
|
|
:content="t('dialog.group.members.representing')">
|
|
<i class="el-icon-collection-tag" style="margin-right: 5px"></i>
|
|
</el-tooltip>
|
|
<el-tooltip v-if="group.myMember.visibility !== 'visible'" placement="top">
|
|
<template #content>
|
|
<span
|
|
>{{ t('dialog.group.members.visibility') }}
|
|
{{ group.myMember.visibility }}</span
|
|
>
|
|
</template>
|
|
<i class="el-icon-view" style="margin-right: 5px"></i>
|
|
</el-tooltip>
|
|
<span>({{ group.memberCount }})</span>
|
|
</span>
|
|
</div>
|
|
<el-dropdown
|
|
:disabled="group.privacy !== 'default'"
|
|
trigger="click"
|
|
size="small"
|
|
style="margin-right: 5px"
|
|
@click.native.stop>
|
|
<el-button size="mini">
|
|
<span v-if="group.myMember.visibility === 'visible'">{{
|
|
t('dialog.group.tags.visible')
|
|
}}</span>
|
|
<span v-else-if="group.myMember.visibility === 'friends'">{{
|
|
t('dialog.group.tags.friends')
|
|
}}</span>
|
|
<span v-else-if="group.myMember.visibility === 'hidden'">{{
|
|
t('dialog.group.tags.hidden')
|
|
}}</span>
|
|
<span v-else>{{ group.myMember.visibility }}</span>
|
|
<i class="el-icon-arrow-down el-icon--right" style="margin-left: 5px"></i>
|
|
</el-button>
|
|
<el-dropdown-menu>
|
|
<el-dropdown-item @click.native="setGroupVisibility(group.id, 'visible')"
|
|
><i
|
|
v-if="group.myMember.visibility === 'visible'"
|
|
class="el-icon-check"></i>
|
|
{{ t('dialog.group.actions.visibility_everyone') }}</el-dropdown-item
|
|
>
|
|
<el-dropdown-item @click.native="setGroupVisibility(group.id, 'friends')"
|
|
><i
|
|
v-if="group.myMember.visibility === 'friends'"
|
|
class="el-icon-check"></i>
|
|
{{ t('dialog.group.actions.visibility_friends') }}</el-dropdown-item
|
|
>
|
|
<el-dropdown-item @click.native="setGroupVisibility(group.id, 'hidden')"
|
|
><i
|
|
v-if="group.myMember.visibility === 'hidden'"
|
|
class="el-icon-check"></i>
|
|
{{ t('dialog.group.actions.visibility_hidden') }}</el-dropdown-item
|
|
>
|
|
</el-dropdown-menu>
|
|
</el-dropdown>
|
|
<!--//- JSON is missing isSubscribedToAnnouncements, can't be implemented-->
|
|
<!--//- el-dropdown(@click.native.stop trigger="click" size="small" style="margin-right:5px")-->
|
|
<!--//- el-tooltip(placement="top" :disabled="hideTooltips")-->
|
|
<!--//- template(#content)-->
|
|
<!--//- span(v-if="group.myMember.isSubscribedToAnnouncements") {{ t('dialog.group.actions.unsubscribe') }}-->
|
|
<!--//- span(v-else) {{ t('dialog.group.actions.subscribe') }}-->
|
|
<!--//- el-button(v-if="group.myMember.isSubscribedToAnnouncements" @click.stop="setGroupSubscription(group.id, false)" circle size="mini")-->
|
|
<!--//- i.el-icon-chat-line-square-->
|
|
<!--//- el-button(v-else circle @click.stop="setGroupSubscription(group.id, true)" size="mini")-->
|
|
<!--//- i.el-icon-chat-square(style="color:#f56c6c")-->
|
|
<el-tooltip
|
|
placement="right"
|
|
:content="t('dialog.user.groups.leave_group_tooltip')"
|
|
:disabled="hideTooltips">
|
|
<el-button
|
|
v-if="shiftHeld"
|
|
size="mini"
|
|
icon="el-icon-close"
|
|
circle
|
|
style="color: #f56c6c; margin-left: 5px"
|
|
@click.stop="leaveGroupPrompt(group.id)">
|
|
</el-button>
|
|
<el-button
|
|
v-else
|
|
size="mini"
|
|
icon="el-icon-delete"
|
|
circle
|
|
style="margin-left: 5px"
|
|
@click.stop="leaveGroupPrompt(group.id)">
|
|
</el-button>
|
|
</el-tooltip>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
<template v-if="userGroups.ownGroups.length > 0">
|
|
<span style="font-weight: bold; font-size: 16px">{{
|
|
t('dialog.user.groups.own_groups')
|
|
}}</span>
|
|
<span style="color: #909399; font-size: 12px; margin-left: 5px"
|
|
>{{ userGroups.ownGroups.length }}/{{
|
|
API.cachedConfig?.constants?.GROUPS?.MAX_OWNED
|
|
}}</span
|
|
>
|
|
<div
|
|
class="x-friend-list"
|
|
style="margin-top: 10px; margin-bottom: 15px; min-height: 60px">
|
|
<div
|
|
v-for="group in userGroups.ownGroups"
|
|
:key="group.id"
|
|
class="x-friend-item x-friend-item-border"
|
|
@click="showGroupDialog(group.id)">
|
|
<div class="avatar">
|
|
<img v-lazy="group.iconUrl" />
|
|
</div>
|
|
<div class="detail">
|
|
<span class="name" v-text="group.name"></span>
|
|
<span class="extra">
|
|
<el-tooltip
|
|
v-if="group.isRepresenting"
|
|
placement="top"
|
|
:content="t('dialog.group.members.representing')">
|
|
<i class="el-icon-collection-tag" style="margin-right: 5px"></i>
|
|
</el-tooltip>
|
|
<el-tooltip v-if="group.memberVisibility !== 'visible'" placement="top">
|
|
<template #content>
|
|
<span
|
|
>{{ t('dialog.group.members.visibility') }}
|
|
{{ group.memberVisibility }}</span
|
|
>
|
|
</template>
|
|
<i class="el-icon-view" style="margin-right: 5px"></i>
|
|
</el-tooltip>
|
|
<span>({{ group.memberCount }})</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template v-if="userGroups.mutualGroups.length > 0">
|
|
<span style="font-weight: bold; font-size: 16px">{{
|
|
t('dialog.user.groups.mutual_groups')
|
|
}}</span>
|
|
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{
|
|
userGroups.mutualGroups.length
|
|
}}</span>
|
|
<div
|
|
class="x-friend-list"
|
|
style="margin-top: 10px; margin-bottom: 15px; min-height: 60px">
|
|
<div
|
|
v-for="group in userGroups.mutualGroups"
|
|
:key="group.id"
|
|
class="x-friend-item x-friend-item-border"
|
|
@click="showGroupDialog(group.id)">
|
|
<div class="avatar">
|
|
<img v-lazy="group.iconUrl" />
|
|
</div>
|
|
<div class="detail">
|
|
<span class="name" v-text="group.name"></span>
|
|
<span class="extra">
|
|
<el-tooltip
|
|
v-if="group.isRepresenting"
|
|
placement="top"
|
|
:content="t('dialog.group.members.representing')">
|
|
<i class="el-icon-collection-tag" style="margin-right: 5px"></i>
|
|
</el-tooltip>
|
|
<el-tooltip v-if="group.memberVisibility !== 'visible'" placement="top">
|
|
<template #content>
|
|
<span
|
|
>{{ t('dialog.group.members.visibility') }}
|
|
{{ group.memberVisibility }}</span
|
|
>
|
|
</template>
|
|
<i class="el-icon-view" style="margin-right: 5px"></i>
|
|
</el-tooltip>
|
|
<span>({{ group.memberCount }})</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template v-if="userGroups.remainingGroups.length > 0">
|
|
<span style="font-weight: bold; font-size: 16px">{{
|
|
t('dialog.user.groups.groups')
|
|
}}</span>
|
|
<span style="color: #909399; font-size: 12px; margin-left: 5px">
|
|
{{ userGroups.remainingGroups.length }}
|
|
<template v-if="API.currentUser.id === userDialog.id">
|
|
/
|
|
<template v-if="API.currentUser.$isVRCPlus">
|
|
{{ API.cachedConfig?.constants?.GROUPS?.MAX_JOINED_PLUS }}
|
|
</template>
|
|
<template v-else>
|
|
{{ API.cachedConfig?.constants?.GROUPS?.MAX_JOINED }}
|
|
</template>
|
|
</template>
|
|
</span>
|
|
<div
|
|
class="x-friend-list"
|
|
style="margin-top: 10px; margin-bottom: 15px; min-height: 60px">
|
|
<div
|
|
v-for="group in userGroups.remainingGroups"
|
|
:key="group.id"
|
|
class="x-friend-item x-friend-item-border"
|
|
@click="showGroupDialog(group.id)">
|
|
<div class="avatar">
|
|
<img v-lazy="group.iconUrl" />
|
|
</div>
|
|
<div class="detail">
|
|
<span class="name" v-text="group.name"></span>
|
|
<span class="extra">
|
|
<el-tooltip
|
|
v-if="group.isRepresenting"
|
|
placement="top"
|
|
:content="t('dialog.group.members.representing')">
|
|
<i class="el-icon-collection-tag" style="margin-right: 5px"></i>
|
|
</el-tooltip>
|
|
<el-tooltip v-if="group.memberVisibility !== 'visible'" placement="top">
|
|
<template #content>
|
|
<span
|
|
>{{ t('dialog.group.members.visibility') }}
|
|
{{ group.memberVisibility }}</span
|
|
>
|
|
</template>
|
|
<i class="el-icon-view" style="margin-right: 5px"></i>
|
|
</el-tooltip>
|
|
<span>({{ group.memberCount }})</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</template>
|
|
</div>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane :label="t('dialog.user.worlds.header')" lazy>
|
|
<div style="display: flex; align-items: center; justify-content: space-between">
|
|
<div style="display: flex; align-items: center">
|
|
<el-button
|
|
type="default"
|
|
:loading="userDialog.isWorldsLoading"
|
|
size="mini"
|
|
icon="el-icon-refresh"
|
|
circle
|
|
@click="refreshUserDialogWorlds()">
|
|
</el-button>
|
|
<span style="margin-left: 5px">{{
|
|
t('dialog.user.worlds.total_count', { count: userDialog.worlds.length })
|
|
}}</span>
|
|
</div>
|
|
<div style="display: flex; align-items: center">
|
|
<span style="margin-right: 5px">{{ t('dialog.user.worlds.sort_by') }}</span>
|
|
<el-dropdown
|
|
trigger="click"
|
|
size="small"
|
|
style="margin-right: 5px"
|
|
:disabled="userDialog.isWorldsLoading"
|
|
@click.native.stop>
|
|
<el-button size="mini">
|
|
<span
|
|
>{{ userDialog.worldSorting.name }}
|
|
<i class="el-icon-arrow-down el-icon--right"></i
|
|
></span>
|
|
</el-button>
|
|
<el-dropdown-menu slot="dropdown">
|
|
<el-dropdown-item
|
|
v-for="(item, key) in userDialogWorldSortingOptions"
|
|
:key="key"
|
|
@click.native="setUserDialogWorldSorting(item)"
|
|
v-text="item.name">
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</el-dropdown>
|
|
<span style="margin: 0 5px">{{ t('dialog.user.worlds.order_by') }}</span>
|
|
<el-dropdown
|
|
trigger="click"
|
|
size="small"
|
|
style="margin-right: 5px"
|
|
:disabled="userDialog.isWorldsLoading"
|
|
@click.native.stop>
|
|
<el-button size="mini">
|
|
<span
|
|
>{{ userDialog.worldOrder.name }}
|
|
<i class="el-icon-arrow-down el-icon--right"></i
|
|
></span>
|
|
</el-button>
|
|
<el-dropdown-menu slot="dropdown">
|
|
<el-dropdown-item
|
|
v-for="(item, key) in userDialogWorldOrderOptions"
|
|
:key="key"
|
|
@click.native="setUserDialogWorldOrder(item)"
|
|
v-text="item.name">
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</el-dropdown>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-loading="userDialog.isWorldsLoading"
|
|
class="x-friend-list"
|
|
style="margin-top: 10px; min-height: 60px">
|
|
<div
|
|
v-for="world in userDialog.worlds"
|
|
:key="world.id"
|
|
class="x-friend-item x-friend-item-border"
|
|
@click="showWorldDialog(world.id)">
|
|
<div class="avatar">
|
|
<img v-lazy="world.thumbnailImageUrl" />
|
|
</div>
|
|
<div class="detail">
|
|
<span class="name" v-text="world.name"></span>
|
|
<span v-if="world.occupants" class="extra">({{ world.occupants }})</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane :label="t('dialog.user.favorite_worlds.header')" lazy>
|
|
<el-button
|
|
v-if="userFavoriteWorlds && userFavoriteWorlds.length > 0"
|
|
type="default"
|
|
:loading="userDialog.isFavoriteWorldsLoading"
|
|
size="small"
|
|
icon="el-icon-refresh"
|
|
circle
|
|
style="position: absolute; right: 15px; bottom: 15px; z-index: 99"
|
|
@click="getUserFavoriteWorlds(userDialog.id)">
|
|
</el-button>
|
|
<el-tabs
|
|
ref="favoriteWorldsRef"
|
|
v-loading="userDialog.isFavoriteWorldsLoading"
|
|
class="zero-margin-tabs"
|
|
type="card"
|
|
stretch
|
|
style="margin-top: 10px; height: 50vh">
|
|
<template v-if="userFavoriteWorlds && userFavoriteWorlds.length > 0">
|
|
<el-tab-pane v-for="(list, index) in userFavoriteWorlds" :key="index" lazy>
|
|
<span slot="label">
|
|
<i
|
|
class="x-status-icon"
|
|
style="margin-right: 6px"
|
|
:class="userFavoriteWorldsStatus(list[1])">
|
|
</i>
|
|
<span style="font-weight: bold; font-size: 14px" v-text="list[0]"></span>
|
|
<span style="color: #909399; font-size: 10px; margin-left: 5px"
|
|
>{{ list[2].length }}/{{ API.favoriteLimits.maxFavoritesPerGroup.world }}</span
|
|
>
|
|
</span>
|
|
<div
|
|
class="x-friend-list"
|
|
style="margin-top: 10px; margin-bottom: 15px; min-height: 60px; max-height: none">
|
|
<div
|
|
v-for="world in list[2]"
|
|
:key="world.favoriteId"
|
|
class="x-friend-item x-friend-item-border"
|
|
@click="showWorldDialog(world.id)">
|
|
<div class="avatar">
|
|
<img v-lazy="world.thumbnailImageUrl" />
|
|
</div>
|
|
<div class="detail">
|
|
<span class="name" v-text="world.name"></span>
|
|
<span v-if="world.occupants" class="extra">({{ world.occupants }})</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</el-tab-pane>
|
|
</template>
|
|
<template v-else-if="!userDialog.isFavoriteWorldsLoading">
|
|
<div style="display: flex; justify-content: center; align-items: center; height: 100%">
|
|
<span style="font-size: 16px">No favorite worlds found.</span>
|
|
</div>
|
|
</template>
|
|
</el-tabs>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane :label="t('dialog.user.avatars.header')" lazy>
|
|
<div style="display: flex; align-items: center; justify-content: space-between">
|
|
<div style="display: flex; align-items: center">
|
|
<el-button
|
|
v-if="userDialog.ref.id === API.currentUser.id"
|
|
type="default"
|
|
:loading="userDialog.isAvatarsLoading"
|
|
size="mini"
|
|
icon="el-icon-refresh"
|
|
circle
|
|
@click="refreshUserDialogAvatars()">
|
|
</el-button>
|
|
<el-button
|
|
v-else
|
|
type="default"
|
|
:loading="userDialog.isAvatarsLoading"
|
|
size="mini"
|
|
icon="el-icon-refresh"
|
|
circle
|
|
@click="setUserDialogAvatarsRemote(userDialog.id)">
|
|
</el-button>
|
|
<span style="margin-left: 5px">{{
|
|
t('dialog.user.avatars.total_count', { count: userDialogAvatars.length })
|
|
}}</span>
|
|
</div>
|
|
<div>
|
|
<template v-if="userDialog.ref.id === API.currentUser.id">
|
|
<span style="margin-right: 5px">{{ t('dialog.user.avatars.sort_by') }}</span>
|
|
<el-dropdown
|
|
trigger="click"
|
|
size="small"
|
|
style="margin-right: 5px"
|
|
:disabled="userDialog.isWorldsLoading"
|
|
@click.native.stop>
|
|
<el-button size="mini">
|
|
<span
|
|
>{{ t(`dialog.user.avatars.sort_by_${userDialog.avatarSorting}`) }}
|
|
<i class="el-icon-arrow-down el-icon--right"></i
|
|
></span>
|
|
</el-button>
|
|
<el-dropdown-menu slot="dropdown">
|
|
<el-dropdown-item
|
|
@click.native="changeUserDialogAvatarSorting('name')"
|
|
v-text="t('dialog.user.avatars.sort_by_name')">
|
|
</el-dropdown-item>
|
|
<el-dropdown-item
|
|
@click.native="changeUserDialogAvatarSorting('update')"
|
|
v-text="t('dialog.user.avatars.sort_by_update')">
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</el-dropdown>
|
|
<span style="margin-right: 5px; margin-left: 10px">{{
|
|
t('dialog.user.avatars.group_by')
|
|
}}</span>
|
|
<el-dropdown
|
|
trigger="click"
|
|
size="small"
|
|
style="margin-right: 5px"
|
|
:disabled="userDialog.isWorldsLoading"
|
|
@click.native.stop>
|
|
<el-button size="mini">
|
|
<span
|
|
>{{ t(`dialog.user.avatars.${userDialog.avatarReleaseStatus}`) }}
|
|
<i class="el-icon-arrow-down el-icon--right"></i
|
|
></span>
|
|
</el-button>
|
|
<el-dropdown-menu slot="dropdown">
|
|
<el-dropdown-item
|
|
@click.native="userDialog.avatarReleaseStatus = 'all'"
|
|
v-text="t('dialog.user.avatars.all')">
|
|
</el-dropdown-item>
|
|
<el-dropdown-item
|
|
@click.native="userDialog.avatarReleaseStatus = 'public'"
|
|
v-text="t('dialog.user.avatars.public')">
|
|
</el-dropdown-item>
|
|
<el-dropdown-item
|
|
@click.native="userDialog.avatarReleaseStatus = 'private'"
|
|
v-text="t('dialog.user.avatars.private')">
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</el-dropdown>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<div class="x-friend-list" style="margin-top: 10px; min-height: 60px; max-height: 50vh">
|
|
<div
|
|
v-for="avatar in userDialogAvatars"
|
|
:key="avatar.id"
|
|
class="x-friend-item x-friend-item-border"
|
|
@click="showAvatarDialog(avatar.id)">
|
|
<div class="avatar">
|
|
<img v-if="avatar.thumbnailImageUrl" v-lazy="avatar.thumbnailImageUrl" />
|
|
</div>
|
|
<div class="detail">
|
|
<span class="name" v-text="avatar.name"></span>
|
|
<span
|
|
v-if="avatar.releaseStatus === 'public'"
|
|
class="extra"
|
|
style="color: #67c23a"
|
|
v-text="avatar.releaseStatus">
|
|
</span>
|
|
<span
|
|
v-else-if="avatar.releaseStatus === 'private'"
|
|
class="extra"
|
|
style="color: #f56c6c"
|
|
v-text="avatar.releaseStatus">
|
|
</span>
|
|
<span v-else class="extra" v-text="avatar.releaseStatus"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane :label="t('dialog.user.json.header')" lazy style="height: 50vh">
|
|
<el-button
|
|
type="default"
|
|
size="mini"
|
|
icon="el-icon-refresh"
|
|
circle
|
|
@click="refreshUserDialogTreeData()">
|
|
</el-button>
|
|
<el-button
|
|
type="default"
|
|
size="mini"
|
|
icon="el-icon-download"
|
|
circle
|
|
style="margin-left: 5px"
|
|
@click="downloadAndSaveJson(userDialog.id, userDialog.ref)">
|
|
</el-button>
|
|
<el-tree :data="userDialog.treeData" style="margin-top: 5px; font-size: 12px">
|
|
<template #default="scope">
|
|
<span>
|
|
<span style="font-weight: bold; margin-right: 5px" v-text="scope.data.key"></span>
|
|
<span v-if="!scope.data.children" v-text="scope.data.value"></span>
|
|
</span>
|
|
</template>
|
|
</el-tree>
|
|
</el-tab-pane>
|
|
</el-tabs>
|
|
</div>
|
|
<SendInviteDialog
|
|
:send-invite-dialog-visible.sync="sendInviteDialogVisible"
|
|
:invite-message-table="inviteMessageTable"
|
|
:send-invite-dialog="sendInviteDialog"
|
|
:upload-image="uploadImage" />
|
|
<SendInviteRequestDialog
|
|
:send-invite-request-dialog-visible.sync="sendInviteRequestDialogVisible"
|
|
:invite-request-message-table="inviteRequestMessageTable"
|
|
:send-invite-dialog="sendInviteDialog"
|
|
:upload-image="uploadImage"
|
|
@inviteImageUpload="inviteImageUpload" />
|
|
<PreviousInstancesUserDialog
|
|
:previous-instances-user-dialog.sync="previousInstancesUserDialog"
|
|
:shift-held="shiftHeld" />
|
|
<PreviousImagesDialog
|
|
:previous-images-dialog-visible.sync="previousImagesDialogVisible"
|
|
:previous-images-table="previousImagesTable" />
|
|
<InviteGroupDialog
|
|
:dialog-data.sync="inviteGroupDialog"
|
|
:vip-friends="vipFriends"
|
|
:online-friends="onlineFriends"
|
|
:offline-friends="offlineFriends"
|
|
:active-friends="activeFriends" />
|
|
<SocialStatusDialog
|
|
:social-status-dialog="socialStatusDialog"
|
|
:social-status-history-table="socialStatusHistoryTable" />
|
|
<LanguageDialog :language-dialog="languageDialog" />
|
|
<BioDialog :bio-dialog="bioDialog" />
|
|
<PronounsDialog :pronouns-dialog="pronounsDialog" />
|
|
<GalleryDialog
|
|
:gallery-dialog-visible.sync="galleryDialogVisible"
|
|
:gallery-dialog-gallery-loading="galleryDialogGalleryLoading"
|
|
:gallery-dialog-icons-loading="galleryDialogIconsLoading"
|
|
:gallery-dialog-emojis-loading="galleryDialogEmojisLoading"
|
|
:gallery-dialog-stickers-loading="galleryDialogStickersLoading"
|
|
:gallery-dialog-prints-loading="galleryDialogPrintsLoading"
|
|
:gallery-table="galleryTable"
|
|
:VRCPlusIconsTable="VRCPlusIconsTable"
|
|
:emoji-table="emojiTable"
|
|
:sticker-table="stickerTable"
|
|
:print-upload-note="printUploadNote"
|
|
:print-crop-border="printCropBorder"
|
|
:print-table="printTable"
|
|
@refreshGalleryTable="refreshGalleryTable"
|
|
@refreshVRCPlusIconsTable="refreshVRCPlusIconsTable"
|
|
@refreshStickerTable="refreshStickerTable"
|
|
@refreshEmojiTable="refreshEmojiTable"
|
|
@refreshPrintTable="refreshPrintTable"
|
|
@closeGalleryDialog="closeGalleryDialog" />
|
|
</safe-dialog>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, getCurrentInstance, inject, nextTick, ref, watch } from 'vue';
|
|
import { useI18n } from 'vue-i18n-bridge';
|
|
import {
|
|
favoriteRequest,
|
|
friendRequest,
|
|
groupRequest,
|
|
imageRequest,
|
|
inviteMessagesRequest,
|
|
miscRequest,
|
|
notificationRequest,
|
|
playerModerationRequest,
|
|
userRequest,
|
|
worldRequest
|
|
} from '../../../api';
|
|
import utils from '../../../classes/utils';
|
|
import { isRealInstance, parseLocation, refreshInstancePlayerCount } from '../../../composables/instance/utils';
|
|
import {
|
|
copyToClipboard,
|
|
downloadAndSaveJson,
|
|
extractFileId,
|
|
getFaviconUrl
|
|
} from '../../../composables/shared/utils';
|
|
import { userDialogGroupSortingOptions } from '../../../composables/user/constants/userDialogGroupSortingOptions';
|
|
import { isFriendOnline, languageClass, userOnlineForTimestamp } from '../../../composables/user/utils';
|
|
import database from '../../../service/database';
|
|
import Location from '../../Location.vue';
|
|
import SendInviteDialog from '../InviteDialog/SendInviteDialog.vue';
|
|
import InviteGroupDialog from '../InviteGroupDialog.vue';
|
|
import PreviousImagesDialog from '../PreviousImagesDialog.vue';
|
|
import BioDialog from './BioDialog.vue';
|
|
import GalleryDialog from './GalleryDialog.vue';
|
|
import LanguageDialog from './LanguageDialog.vue';
|
|
import PreviousInstancesUserDialog from './PreviousInstancesUserDialog.vue';
|
|
import PronounsDialog from './PronounsDialog.vue';
|
|
import SendInviteRequestDialog from './SendInviteRequestDialog.vue';
|
|
import SocialStatusDialog from './SocialStatusDialog.vue';
|
|
|
|
const { t } = useI18n();
|
|
|
|
const { proxy } = getCurrentInstance();
|
|
const { $message, $confirm } = proxy;
|
|
|
|
const API = inject('API');
|
|
const showFullscreenImageDialog = inject('showFullscreenImageDialog');
|
|
|
|
const userImage = inject('userImage');
|
|
const showLaunchDialog = inject('showLaunchDialog');
|
|
const showUserDialog = inject('showUserDialog');
|
|
const showGroupDialog = inject('showGroupDialog');
|
|
const openExternalLink = inject('openExternalLink');
|
|
const showWorldDialog = inject('showWorldDialog');
|
|
const showAvatarDialog = inject('showAvatarDialog');
|
|
const showFavoriteDialog = inject('showFavoriteDialog');
|
|
const adjustDialogZ = inject('adjustDialogZ');
|
|
const userStatusClass = inject('userStatusClass');
|
|
|
|
const props = defineProps({
|
|
userDialog: {
|
|
type: Object,
|
|
default: () => ({})
|
|
},
|
|
languageDialog: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
hideTooltips: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
lastLocation: {
|
|
type: Object,
|
|
default: () => ({})
|
|
},
|
|
lastLocationDestination: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
isGameRunning: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
checkCanInvite: {
|
|
type: Function,
|
|
default: () => {}
|
|
},
|
|
updateInstanceInfo: {
|
|
type: Number,
|
|
default: () => {}
|
|
},
|
|
hideUserNotes: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
hideUserMemos: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
userOnlineFor: {
|
|
type: Function,
|
|
default: () => {}
|
|
},
|
|
userDialogWorldSortingOptions: {
|
|
type: Object,
|
|
default: () => ({})
|
|
},
|
|
userDialogWorldOrderOptions: {
|
|
type: Object,
|
|
default: () => ({})
|
|
},
|
|
avatarRemoteDatabase: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
friendLogTable: {
|
|
type: Object,
|
|
default: () => ({})
|
|
},
|
|
lookupAvatars: {
|
|
type: Function,
|
|
default: () => {}
|
|
},
|
|
updateInGameGroupOrder: {
|
|
type: Function,
|
|
default: () => {}
|
|
},
|
|
// SendInviteDialog
|
|
inviteMessageTable: {
|
|
type: Object,
|
|
default: () => ({})
|
|
},
|
|
uploadImage: {
|
|
type: String
|
|
},
|
|
inviteRequestMessageTable: {
|
|
type: Object,
|
|
default: () => ({})
|
|
},
|
|
inGameGroupOrder: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
shiftHeld: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
vipFriends: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
onlineFriends: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
offlineFriends: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
activeFriends: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
// Gallery Dialog
|
|
galleryDialogVisible: {
|
|
type: Boolean,
|
|
required: true
|
|
},
|
|
galleryDialogGalleryLoading: {
|
|
type: Boolean,
|
|
required: true
|
|
},
|
|
galleryDialogIconsLoading: {
|
|
type: Boolean,
|
|
required: true
|
|
},
|
|
galleryDialogEmojisLoading: {
|
|
type: Boolean,
|
|
required: true
|
|
},
|
|
galleryDialogStickersLoading: {
|
|
type: Boolean,
|
|
required: true
|
|
},
|
|
galleryDialogPrintsLoading: {
|
|
type: Boolean,
|
|
required: true
|
|
},
|
|
galleryTable: {
|
|
type: Array,
|
|
required: true
|
|
},
|
|
// eslint-disable-next-line vue/prop-name-casing
|
|
VRCPlusIconsTable: {
|
|
type: Array,
|
|
required: true
|
|
},
|
|
emojiTable: {
|
|
type: Array,
|
|
required: true
|
|
},
|
|
stickerTable: {
|
|
type: Array,
|
|
required: true
|
|
},
|
|
printUploadNote: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
printCropBorder: {
|
|
type: Boolean,
|
|
required: true
|
|
},
|
|
printTable: {
|
|
type: Array,
|
|
required: true
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits([
|
|
'sortUserDialogAvatars',
|
|
'logout',
|
|
'showAvatarAuthorDialog',
|
|
'showGalleryDialog',
|
|
'saveCurrentUserGroups',
|
|
'refreshUserDialogAvatars',
|
|
'refreshUserDialogTreeData',
|
|
'saveUserMemo',
|
|
'setGroupVisibility',
|
|
'leaveGroupPrompt',
|
|
'clearInviteImageUpload',
|
|
'refreshGalleryTable',
|
|
'refreshVRCPlusIconsTable',
|
|
'refreshStickerTable',
|
|
'refreshEmojiTable',
|
|
'refreshPrintTable',
|
|
'closeGalleryDialog'
|
|
]);
|
|
|
|
watch(
|
|
() => props.userDialog.loading,
|
|
() => {
|
|
if (props.userDialog.visible) {
|
|
nextTick(() => adjustDialogZ(userDialogRef.value.$el));
|
|
!props.userDialog.loading && toggleLastActiveTab(props.userDialog.id);
|
|
}
|
|
}
|
|
);
|
|
|
|
const userDialogTabsRef = ref(null);
|
|
const userDialogRef = ref(null);
|
|
|
|
const userDialogGroupEditMode = ref(false);
|
|
const userDialogGroupEditGroups = ref([]);
|
|
const userDialogLastActiveTab = ref('');
|
|
const userDialogLastGroup = ref('');
|
|
const userDialogLastAvatar = ref('');
|
|
const userDialogLastWorld = ref('');
|
|
const userDialogLastFavoriteWorld = ref('');
|
|
const userFavoriteWorlds = ref([]);
|
|
const userGroups = ref({
|
|
groups: [],
|
|
ownGroups: [],
|
|
mutualGroups: [],
|
|
remainingGroups: []
|
|
});
|
|
|
|
const favoriteWorldsRef = ref(null);
|
|
|
|
const sendInviteDialogVisible = ref(false);
|
|
const sendInviteDialog = ref({ message: '', messageSlot: 0, userId: '', messageType: '', params: {} });
|
|
const sendInviteRequestDialogVisible = ref(false);
|
|
|
|
const previousInstancesUserDialog = ref({
|
|
visible: false,
|
|
openFlg: false,
|
|
userRef: {}
|
|
});
|
|
|
|
const previousImagesDialogVisible = ref(false);
|
|
const previousImagesTable = ref([]);
|
|
|
|
const inviteGroupDialog = ref({
|
|
visible: false,
|
|
loading: false,
|
|
groupId: '',
|
|
groupName: '',
|
|
userId: '',
|
|
userIds: [],
|
|
userObject: {}
|
|
});
|
|
|
|
const socialStatusDialog = ref({
|
|
visible: false,
|
|
loading: false,
|
|
status: '',
|
|
statusDescription: ''
|
|
});
|
|
const socialStatusHistoryTable = ref({
|
|
data: [],
|
|
tableProps: {
|
|
stripe: true,
|
|
size: 'mini'
|
|
},
|
|
layout: 'table'
|
|
});
|
|
|
|
const bioDialog = ref({
|
|
visible: false,
|
|
loading: false,
|
|
bio: '',
|
|
bioLinks: []
|
|
});
|
|
|
|
const pronounsDialog = ref({
|
|
visible: false,
|
|
loading: false,
|
|
pronouns: ''
|
|
});
|
|
|
|
const userDialogAvatars = computed(() => {
|
|
const { avatars, avatarReleaseStatus } = props.userDialog;
|
|
if (avatarReleaseStatus === 'public' || avatarReleaseStatus === 'private') {
|
|
return avatars.filter((avatar) => avatar.releaseStatus === avatarReleaseStatus);
|
|
}
|
|
return avatars;
|
|
});
|
|
|
|
function userFavoriteWorldsStatus(visibility) {
|
|
const style = {};
|
|
if (visibility === 'public') {
|
|
style.green = true;
|
|
} else if (visibility === 'friends') {
|
|
style.blue = true;
|
|
} else {
|
|
style.red = true;
|
|
}
|
|
return style;
|
|
}
|
|
|
|
function cleanNote(note) {
|
|
// remove newlines because they aren't supported
|
|
props.userDialog.note = note.replace(/[\r\n]/g, '');
|
|
}
|
|
|
|
function closeGalleryDialog() {
|
|
emit('closeGalleryDialog');
|
|
}
|
|
|
|
function toggleLastActiveTab(userId) {
|
|
if (userDialogTabsRef.value.currentName === '0') {
|
|
userDialogLastActiveTab.value = t('dialog.user.info.header');
|
|
} else if (userDialogTabsRef.value.currentName === '1') {
|
|
userDialogLastActiveTab.value = t('dialog.user.groups.header');
|
|
if (userDialogLastGroup.value !== userId) {
|
|
userDialogLastGroup.value = userId;
|
|
getUserGroups(userId);
|
|
}
|
|
} else if (userDialogTabsRef.value.currentName === '2') {
|
|
userDialogLastActiveTab.value = t('dialog.user.worlds.header');
|
|
setUserDialogWorlds(userId);
|
|
if (userDialogLastWorld.value !== userId) {
|
|
userDialogLastWorld.value = userId;
|
|
refreshUserDialogWorlds();
|
|
}
|
|
} else if (userDialogTabsRef.value.currentName === '3') {
|
|
userDialogLastActiveTab.value = t('dialog.user.favorite_worlds.header');
|
|
if (userDialogLastFavoriteWorld.value !== userId) {
|
|
userDialogLastFavoriteWorld.value = userId;
|
|
getUserFavoriteWorlds(userId);
|
|
}
|
|
} else if (userDialogTabsRef.value.currentName === '4') {
|
|
userDialogLastActiveTab.value = t('dialog.user.avatars.header');
|
|
setUserDialogAvatars(userId);
|
|
userDialogLastAvatar.value = userId;
|
|
if (userId === API.currentUser.id) {
|
|
refreshUserDialogAvatars();
|
|
}
|
|
setUserDialogAvatarsRemote(userId);
|
|
} else if (userDialogTabsRef.value.currentName === '5') {
|
|
userDialogLastActiveTab.value = t('dialog.user.json.header');
|
|
refreshUserDialogTreeData();
|
|
}
|
|
}
|
|
|
|
function showPronounsDialog() {
|
|
const D = pronounsDialog.value;
|
|
D.pronouns = API.currentUser.pronouns;
|
|
D.visible = true;
|
|
}
|
|
|
|
function showLanguageDialog() {
|
|
const D = props.languageDialog;
|
|
D.visible = true;
|
|
}
|
|
|
|
function showSocialStatusDialog() {
|
|
// this.$nextTick(() =>
|
|
// $app.adjustDialogZ(this.$refs.socialStatusDialog.$el)
|
|
// );
|
|
const D = socialStatusDialog.value;
|
|
const { statusHistory } = API.currentUser;
|
|
const statusHistoryArray = [];
|
|
for (let i = 0; i < statusHistory.length; ++i) {
|
|
const addStatus = {
|
|
no: i + 1,
|
|
status: statusHistory[i]
|
|
};
|
|
statusHistoryArray.push(addStatus);
|
|
}
|
|
socialStatusHistoryTable.value.data = statusHistoryArray;
|
|
D.status = API.currentUser.status;
|
|
D.statusDescription = API.currentUser.statusDescription;
|
|
D.visible = true;
|
|
}
|
|
|
|
async function setUserDialogAvatarsRemote(userId) {
|
|
if (props.avatarRemoteDatabase && userId !== API.currentUser.id) {
|
|
props.userDialog.isAvatarsLoading = true;
|
|
const data = await props.lookupAvatars('authorId', userId);
|
|
const avatars = new Set();
|
|
userDialogAvatars.value.forEach((avatar) => {
|
|
avatars.add(avatar.id, avatar);
|
|
});
|
|
if (data && typeof data === 'object') {
|
|
data.forEach((avatar) => {
|
|
if (avatar.id && !avatars.has(avatar.id)) {
|
|
if (avatar.authorId === userId) {
|
|
props.userDialog.avatars.push(avatar);
|
|
} else {
|
|
console.error(`Avatar authorId mismatch for ${avatar.id} - ${avatar.name}`);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
props.userDialog.avatarSorting = 'name';
|
|
props.userDialog.avatarReleaseStatus = 'all';
|
|
props.userDialog.isAvatarsLoading = false;
|
|
}
|
|
sortUserDialogAvatars(props.userDialog.avatars);
|
|
}
|
|
|
|
async function toggleBadgeVisibility(badge) {
|
|
if (badge.hidden) {
|
|
badge.showcased = false;
|
|
}
|
|
const args = await miscRequest.updateBadge({
|
|
badgeId: badge.badgeId,
|
|
hidden: badge.hidden,
|
|
showcased: badge.showcased
|
|
});
|
|
handleBadgeUpdate(args);
|
|
}
|
|
|
|
async function toggleBadgeShowcased(badge) {
|
|
if (badge.showcased) {
|
|
badge.hidden = false;
|
|
}
|
|
const args = await miscRequest.updateBadge({
|
|
badgeId: badge.badgeId,
|
|
hidden: badge.hidden,
|
|
showcased: badge.showcased
|
|
});
|
|
handleBadgeUpdate(args);
|
|
}
|
|
|
|
function handleBadgeUpdate(args) {
|
|
// API.$on('BADGE:UPDATE')
|
|
if (args.json) {
|
|
$message({
|
|
message: t('message.badge.updated'),
|
|
type: 'success'
|
|
});
|
|
}
|
|
}
|
|
|
|
function setPlayerModeration(userId, type) {
|
|
const D = props.userDialog;
|
|
AppApi.SetVRChatUserModeration(API.currentUser.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 {
|
|
$message({
|
|
message: t('message.avatar.change_moderation_failed'),
|
|
type: 'error'
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function showSendInviteDialog(params, userId) {
|
|
sendInviteDialog.value = {
|
|
params,
|
|
userId,
|
|
messageType: 'invite'
|
|
};
|
|
inviteMessagesRequest.refreshInviteMessageTableData('message');
|
|
clearInviteImageUpload();
|
|
sendInviteDialogVisible.value = true;
|
|
}
|
|
|
|
function showSendInviteRequestDialog(params, userId) {
|
|
sendInviteDialog.value = {
|
|
params,
|
|
userId,
|
|
messageType: 'requestInvite'
|
|
};
|
|
inviteMessagesRequest.refreshInviteMessageTableData('request');
|
|
clearInviteImageUpload();
|
|
sendInviteRequestDialogVisible.value = true;
|
|
}
|
|
|
|
async function checkPreviousImageAvailable(images) {
|
|
previousImagesTable.value = [];
|
|
for (const image of images) {
|
|
if (image.file && image.file.url) {
|
|
const response = await fetch(image.file.url, {
|
|
method: 'HEAD',
|
|
redirect: 'follow'
|
|
}).catch((error) => {
|
|
console.log(error);
|
|
});
|
|
if (response.status === 200) {
|
|
previousImagesTable.value.push(image);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function displayPreviousImages() {
|
|
previousImagesTable.value = [];
|
|
const imageUrl = props.userDialog.ref.currentAvatarImageUrl;
|
|
|
|
const fileId = extractFileId(imageUrl);
|
|
if (!fileId) {
|
|
return;
|
|
}
|
|
const params = {
|
|
fileId
|
|
};
|
|
previousImagesDialogVisible.value = true;
|
|
|
|
imageRequest.getAvatarImages(params).then((args) => {
|
|
const images = [];
|
|
args.json.versions.forEach((item) => {
|
|
if (!item.deleted) {
|
|
images.unshift(item);
|
|
}
|
|
});
|
|
checkPreviousImageAvailable(images);
|
|
});
|
|
}
|
|
|
|
function showInviteGroupDialog(groupId, userId) {
|
|
const D = inviteGroupDialog.value;
|
|
D.userIds = '';
|
|
D.groups = [];
|
|
D.groupId = groupId;
|
|
D.groupName = groupId;
|
|
D.userId = userId;
|
|
D.userObject = {};
|
|
D.visible = true;
|
|
}
|
|
|
|
function userDialogCommand(command) {
|
|
let L;
|
|
const D = props.userDialog;
|
|
if (D.visible === false) {
|
|
return;
|
|
}
|
|
if (command === 'Refresh') {
|
|
showUserDialog(D.id);
|
|
} 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 === 'Logout') {
|
|
logout();
|
|
} else if (command === 'Request Invite') {
|
|
notificationRequest
|
|
.sendRequestInvite(
|
|
{
|
|
platform: 'standalonewindows'
|
|
},
|
|
D.id
|
|
)
|
|
.then((args) => {
|
|
$message('Request invite sent');
|
|
return args;
|
|
});
|
|
} else if (command === 'Invite Message') {
|
|
L = parseLocation(props.lastLocation.location);
|
|
worldRequest
|
|
.getCachedWorld({
|
|
worldId: L.worldId
|
|
})
|
|
.then((args) => {
|
|
showSendInviteDialog(
|
|
{
|
|
instanceId: props.lastLocation.location,
|
|
worldId: props.lastLocation.location,
|
|
worldName: args.ref.name
|
|
},
|
|
D.id
|
|
);
|
|
});
|
|
} else if (command === 'Request Invite Message') {
|
|
showSendInviteRequestDialog(
|
|
{
|
|
platform: 'standalonewindows'
|
|
},
|
|
D.id
|
|
);
|
|
} else if (command === 'Invite') {
|
|
let currentLocation = props.lastLocation.location;
|
|
if (props.lastLocation.location === 'traveling') {
|
|
currentLocation = props.lastLocationDestination;
|
|
}
|
|
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) => {
|
|
$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 {
|
|
$message({
|
|
message: 'No fallback avatar set',
|
|
type: 'error'
|
|
});
|
|
}
|
|
} else if (command === 'Previous Images') {
|
|
displayPreviousImages();
|
|
} else if (command === 'Previous Instances') {
|
|
showPreviousInstancesUserDialog(D.ref);
|
|
} else if (command === 'Manage Gallery') {
|
|
showGalleryDialog();
|
|
} else if (command === 'Invite To Group') {
|
|
showInviteGroupDialog('', D.id);
|
|
// } else if (command === 'Send Boop') {
|
|
// this.showSendBoopDialog(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 {
|
|
const i18nPreFix = 'dialog.user.actions.';
|
|
const formattedCommand = command.toLowerCase().replace(/ /g, '_');
|
|
const displayCommandText = t(`${i18nPreFix}${formattedCommand}`).includes('i18nPreFix')
|
|
? command
|
|
: t(`${i18nPreFix}${formattedCommand}`);
|
|
|
|
$confirm(
|
|
t('confirm.message', {
|
|
command: displayCommandText
|
|
}),
|
|
t('confirm.title'),
|
|
{
|
|
confirmButtonText: t('confirm.confirm_button'),
|
|
cancelButtonText: t('confirm.cancel_button'),
|
|
type: 'info',
|
|
callback: (action) => {
|
|
if (action === 'confirm') {
|
|
performUserDialogCommand(command, D.id);
|
|
}
|
|
}
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
function handleSendFriendRequest(args) {
|
|
// API.$on('FRIEND:REQUEST')
|
|
const ref = API.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
|
|
};
|
|
props.friendLogTable.data.push(friendLogHistory);
|
|
database.addFriendLogHistory(friendLogHistory);
|
|
|
|
// API.$on('FRIEND:REQUEST')
|
|
const D = props.userDialog;
|
|
if (D.visible === false || D.id !== args.params.userId) {
|
|
return;
|
|
}
|
|
if (args.json.success) {
|
|
D.isFriend = true;
|
|
} else {
|
|
D.outgoingRequest = true;
|
|
}
|
|
}
|
|
|
|
function handleCancelFriendRequest(args) {
|
|
// API.$on('FRIEND:REQUEST:CANCEL')
|
|
const ref = API.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
|
|
};
|
|
props.friendLogTable.data.push(friendLogHistory);
|
|
database.addFriendLogHistory(friendLogHistory);
|
|
|
|
// API.$on('FRIEND:REQUEST:CANCEL')
|
|
const D = props.userDialog;
|
|
if (D.visible === false || D.id !== args.params.userId) {
|
|
return;
|
|
}
|
|
D.outgoingRequest = false;
|
|
}
|
|
|
|
function handleSendPlayerModeration(args) {
|
|
// API.$on('PLAYER-MODERATION:SEND')
|
|
const ref = {
|
|
json: args.json,
|
|
params: {
|
|
playerModerationId: args.json.id
|
|
}
|
|
};
|
|
API.$emit('PLAYER-MODERATION', ref);
|
|
API.$emit('PLAYER-MODERATION:@SEND', ref);
|
|
}
|
|
|
|
async function performUserDialogCommand(command, userId) {
|
|
let key;
|
|
switch (command) {
|
|
case 'Delete Favorite':
|
|
favoriteRequest.deleteFavorite({
|
|
objectId: userId
|
|
});
|
|
break;
|
|
case 'Accept Friend Request':
|
|
key = API.getFriendRequest(userId);
|
|
if (key === '') {
|
|
const args = await friendRequest.sendFriendRequest({
|
|
userId
|
|
});
|
|
handleSendFriendRequest(args);
|
|
} else {
|
|
notificationRequest.acceptFriendRequestNotification({
|
|
notificationId: key
|
|
});
|
|
}
|
|
break;
|
|
case 'Decline Friend Request':
|
|
key = API.getFriendRequest(userId);
|
|
if (key === '') {
|
|
const args = await friendRequest.cancelFriendRequest({
|
|
userId
|
|
});
|
|
handleCancelFriendRequest(args);
|
|
} else {
|
|
notificationRequest.hideNotification({
|
|
notificationId: key
|
|
});
|
|
}
|
|
break;
|
|
case 'Cancel Friend Request': {
|
|
const args = await friendRequest.cancelFriendRequest({
|
|
userId
|
|
});
|
|
handleCancelFriendRequest(args);
|
|
break;
|
|
}
|
|
case 'Send Friend Request': {
|
|
const args = await friendRequest.sendFriendRequest({
|
|
userId
|
|
});
|
|
handleSendFriendRequest(args);
|
|
break;
|
|
}
|
|
case 'Moderation Unblock':
|
|
playerModerationRequest.deletePlayerModeration({
|
|
moderated: userId,
|
|
type: 'block'
|
|
});
|
|
break;
|
|
case 'Moderation Block': {
|
|
const args = await playerModerationRequest.sendPlayerModeration({
|
|
moderated: userId,
|
|
type: 'block'
|
|
});
|
|
handleSendPlayerModeration(args);
|
|
break;
|
|
}
|
|
case 'Moderation Unmute':
|
|
playerModerationRequest.deletePlayerModeration({
|
|
moderated: userId,
|
|
type: 'mute'
|
|
});
|
|
break;
|
|
case 'Moderation Mute': {
|
|
const args = await playerModerationRequest.sendPlayerModeration({
|
|
moderated: userId,
|
|
type: 'mute'
|
|
});
|
|
handleSendPlayerModeration(args);
|
|
break;
|
|
}
|
|
case 'Moderation Enable Avatar Interaction':
|
|
playerModerationRequest.deletePlayerModeration({
|
|
moderated: userId,
|
|
type: 'interactOff'
|
|
});
|
|
break;
|
|
case 'Moderation Disable Avatar Interaction': {
|
|
const args = await playerModerationRequest.sendPlayerModeration({
|
|
moderated: userId,
|
|
type: 'interactOff'
|
|
});
|
|
handleSendPlayerModeration(args);
|
|
break;
|
|
}
|
|
case 'Moderation Enable Chatbox':
|
|
playerModerationRequest.deletePlayerModeration({
|
|
moderated: userId,
|
|
type: 'muteChat'
|
|
});
|
|
break;
|
|
case 'Moderation Disable Chatbox': {
|
|
const args = await playerModerationRequest.sendPlayerModeration({
|
|
moderated: userId,
|
|
type: 'muteChat'
|
|
});
|
|
handleSendPlayerModeration(args);
|
|
break;
|
|
}
|
|
case 'Report Hacking':
|
|
reportUserForHacking(userId);
|
|
break;
|
|
case 'Unfriend':
|
|
friendRequest.deleteFriend({
|
|
userId
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
|
|
function reportUserForHacking(userId) {
|
|
miscRequest.reportUser({
|
|
userId,
|
|
contentType: 'user',
|
|
reason: 'behavior-hacking',
|
|
type: 'report'
|
|
});
|
|
}
|
|
|
|
async function getUserGroups(userId) {
|
|
props.userDialog.isGroupsLoading = true;
|
|
userGroups.value = {
|
|
groups: [],
|
|
ownGroups: [],
|
|
mutualGroups: [],
|
|
remainingGroups: []
|
|
};
|
|
const args = await groupRequest.getGroups({ userId });
|
|
if (userId !== props.userDialog.id) {
|
|
props.userDialog.isGroupsLoading = false;
|
|
return;
|
|
}
|
|
if (userId === API.currentUser.id) {
|
|
// update current user groups
|
|
API.currentUserGroups.clear();
|
|
args.json.forEach((group) => {
|
|
const ref = API.applyGroup(group);
|
|
if (!API.currentUserGroups.has(group.id)) {
|
|
API.currentUserGroups.set(group.id, ref);
|
|
}
|
|
});
|
|
|
|
saveCurrentUserGroups();
|
|
}
|
|
userGroups.value.groups = args.json;
|
|
for (let i = 0; i < args.json.length; ++i) {
|
|
const group = args.json[i];
|
|
if (!group?.id) {
|
|
console.error('getUserGroups, group ID is missing', group);
|
|
continue;
|
|
}
|
|
if (group.ownerId === userId) {
|
|
userGroups.value.ownGroups.unshift(group);
|
|
}
|
|
if (userId === API.currentUser.id) {
|
|
// skip mutual groups for current user
|
|
if (group.ownerId !== userId) {
|
|
userGroups.value.remainingGroups.unshift(group);
|
|
}
|
|
continue;
|
|
}
|
|
if (group.mutualGroup) {
|
|
userGroups.value.mutualGroups.unshift(group);
|
|
}
|
|
if (!group.mutualGroup && group.ownerId !== userId) {
|
|
userGroups.value.remainingGroups.unshift(group);
|
|
}
|
|
}
|
|
if (userId === API.currentUser.id) {
|
|
props.userDialog.groupSorting = userDialogGroupSortingOptions.inGame;
|
|
} else if (props.userDialog.groupSorting === userDialogGroupSortingOptions.inGame) {
|
|
props.userDialog.groupSorting = userDialogGroupSortingOptions.alphabetical;
|
|
}
|
|
await sortCurrentUserGroups();
|
|
props.userDialog.isGroupsLoading = false;
|
|
}
|
|
|
|
function compareByMemberCount(a, b) {
|
|
if (typeof a.memberCount !== 'number' || typeof b.memberCount !== 'number') {
|
|
return 0;
|
|
}
|
|
return a.memberCount - b.memberCount;
|
|
}
|
|
|
|
function sortGroupsByInGame(a, b) {
|
|
const aIndex = props.inGameGroupOrder.indexOf(a?.id);
|
|
const bIndex = props.inGameGroupOrder.indexOf(b?.id);
|
|
if (aIndex === -1 && bIndex === -1) {
|
|
return 0;
|
|
}
|
|
if (aIndex === -1) {
|
|
return 1;
|
|
}
|
|
if (bIndex === -1) {
|
|
return -1;
|
|
}
|
|
return aIndex - bIndex;
|
|
}
|
|
|
|
async function sortCurrentUserGroups() {
|
|
const D = props.userDialog;
|
|
let sortMethod = function () {};
|
|
|
|
switch (D.groupSorting.value) {
|
|
case 'alphabetical':
|
|
sortMethod = utils.compareByName;
|
|
break;
|
|
case 'members':
|
|
sortMethod = compareByMemberCount;
|
|
break;
|
|
case 'inGame':
|
|
sortMethod = sortGroupsByInGame;
|
|
await props.updateInGameGroupOrder();
|
|
break;
|
|
}
|
|
|
|
userGroups.value.ownGroups.sort(sortMethod);
|
|
userGroups.value.mutualGroups.sort(sortMethod);
|
|
userGroups.value.remainingGroups.sort(sortMethod);
|
|
}
|
|
|
|
function setUserDialogAvatars(userId) {
|
|
const avatars = new Set();
|
|
userDialogAvatars.value.forEach((avatar) => {
|
|
avatars.add(avatar.id, avatar);
|
|
});
|
|
for (const ref of API.cachedAvatars.values()) {
|
|
if (ref.authorId === userId && !avatars.has(ref.id)) {
|
|
props.userDialog.avatars.push(ref);
|
|
}
|
|
}
|
|
sortUserDialogAvatars(props.userDialog.avatars);
|
|
}
|
|
|
|
function setUserDialogWorlds(userId) {
|
|
const worlds = [];
|
|
for (const ref of API.cachedWorlds.values()) {
|
|
if (ref.authorId === userId) {
|
|
worlds.push(ref);
|
|
}
|
|
}
|
|
props.userDialog.worlds = worlds;
|
|
}
|
|
|
|
function refreshUserDialogWorlds() {
|
|
const D = props.userDialog;
|
|
if (D.isWorldsLoading) {
|
|
return;
|
|
}
|
|
D.isWorldsLoading = true;
|
|
const params = {
|
|
n: 50,
|
|
offset: 0,
|
|
sort: props.userDialog.worldSorting.value,
|
|
order: props.userDialog.worldOrder.value,
|
|
// user: 'friends',
|
|
userId: D.id,
|
|
releaseStatus: 'public'
|
|
};
|
|
if (params.userId === API.currentUser.id) {
|
|
params.user = 'me';
|
|
params.releaseStatus = 'all';
|
|
}
|
|
const map = new Map();
|
|
for (const ref of API.cachedWorlds.values()) {
|
|
if (ref.authorId === D.id && (ref.authorId === API.currentUser.id || ref.releaseStatus === 'public')) {
|
|
API.cachedWorlds.delete(ref.id);
|
|
}
|
|
}
|
|
API.bulk({
|
|
fn: worldRequest.getWorlds,
|
|
N: -1,
|
|
params,
|
|
handle: (args) => {
|
|
for (const json of args.json) {
|
|
const $ref = API.cachedWorlds.get(json.id);
|
|
if (typeof $ref !== 'undefined') {
|
|
map.set($ref.id, $ref);
|
|
}
|
|
}
|
|
},
|
|
done: () => {
|
|
if (D.id === params.userId) {
|
|
setUserDialogWorlds(D.id);
|
|
}
|
|
D.isWorldsLoading = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
async function getUserFavoriteWorlds(userId) {
|
|
props.userDialog.isFavoriteWorldsLoading = true;
|
|
if (favoriteWorldsRef.value) {
|
|
favoriteWorldsRef.value.currentName = '0'; // select first tab
|
|
}
|
|
userFavoriteWorlds.value = [];
|
|
const worldLists = [];
|
|
let params = {
|
|
ownerId: userId,
|
|
n: 100
|
|
};
|
|
const json = await API.call('favorite/groups', {
|
|
method: 'GET',
|
|
params
|
|
});
|
|
for (let i = 0; i < json.length; ++i) {
|
|
const list = json[i];
|
|
if (list.type !== 'world') {
|
|
continue;
|
|
}
|
|
params = {
|
|
n: 100,
|
|
offset: 0,
|
|
userId,
|
|
tag: list.name
|
|
};
|
|
try {
|
|
const args = await favoriteRequest.getFavoriteWorlds(params);
|
|
worldLists.push([list.displayName, list.visibility, args.json]);
|
|
} catch (err) {
|
|
console.error('getUserFavoriteWorlds', err);
|
|
}
|
|
}
|
|
userFavoriteWorlds.value = worldLists;
|
|
props.userDialog.isFavoriteWorldsLoading = false;
|
|
}
|
|
|
|
function userDialogTabClick(obj) {
|
|
const userId = props.userDialog.id;
|
|
if (userDialogLastActiveTab.value === obj.label) {
|
|
return;
|
|
}
|
|
if (obj.label === t('dialog.user.groups.header')) {
|
|
if (userDialogLastGroup.value !== userId) {
|
|
userDialogLastGroup.value = userId;
|
|
getUserGroups(userId);
|
|
}
|
|
} else if (obj.label === t('dialog.user.avatars.header')) {
|
|
setUserDialogAvatars(userId);
|
|
if (userDialogLastAvatar.value !== userId) {
|
|
userDialogLastAvatar.value = userId;
|
|
if (userId === API.currentUser.id) {
|
|
refreshUserDialogAvatars();
|
|
} else {
|
|
setUserDialogAvatarsRemote(userId);
|
|
}
|
|
}
|
|
} else if (obj.label === t('dialog.user.worlds.header')) {
|
|
setUserDialogWorlds(userId);
|
|
if (userDialogLastWorld.value !== userId) {
|
|
userDialogLastWorld.value = userId;
|
|
refreshUserDialogWorlds();
|
|
}
|
|
} else if (obj.label === t('dialog.user.favorite_worlds.header')) {
|
|
if (userDialogLastFavoriteWorld.value !== userId) {
|
|
userDialogLastFavoriteWorld.value = userId;
|
|
getUserFavoriteWorlds(userId);
|
|
}
|
|
} else if (obj.label === t('dialog.user.json.header')) {
|
|
refreshUserDialogTreeData();
|
|
}
|
|
userDialogLastActiveTab.value = obj.label;
|
|
}
|
|
|
|
function checkNote(ref, note) {
|
|
if (ref.note !== note) {
|
|
addNote(ref.id, note);
|
|
}
|
|
}
|
|
|
|
async function addNote(userId, note) {
|
|
if (props.userDialog.id === userId) {
|
|
props.userDialog.noteSaving = true;
|
|
}
|
|
const args = await miscRequest.saveNote({
|
|
targetUserId: userId,
|
|
note
|
|
});
|
|
handleNoteChange(args);
|
|
}
|
|
|
|
|
|
|
|
function handleNoteChange(args) {
|
|
// API.$on('NOTE')
|
|
let _note = '';
|
|
let targetUserId = '';
|
|
if (typeof args.json !== 'undefined') {
|
|
_note = utils.replaceBioSymbols(args.json.note);
|
|
}
|
|
if (typeof args.params !== 'undefined') {
|
|
targetUserId = args.params.targetUserId;
|
|
}
|
|
if (targetUserId === props.userDialog.id) {
|
|
if (_note === args.params.note) {
|
|
props.userDialog.noteSaving = false;
|
|
props.userDialog.note = _note;
|
|
} else {
|
|
// response is cached sadge :<
|
|
userRequest.getUser({ userId: targetUserId });
|
|
}
|
|
}
|
|
const ref = API.cachedUsers.get(targetUserId);
|
|
if (typeof ref !== 'undefined') {
|
|
ref.note = _note;
|
|
}
|
|
}
|
|
|
|
async function deleteNote(userId) {
|
|
if (props.userDialog.id === userId) {
|
|
props.userDialog.noteSaving = true;
|
|
}
|
|
const args = await miscRequest.saveNote({
|
|
targetUserId: userId,
|
|
note: ''
|
|
});
|
|
handleNoteChange(args);
|
|
}
|
|
|
|
function onUserMemoChange() {
|
|
const D = props.userDialog;
|
|
saveUserMemo(D.id, D.memo);
|
|
}
|
|
|
|
function showBioDialog() {
|
|
const D = bioDialog.value;
|
|
D.bio = API.currentUser.bio;
|
|
D.bioLinks = API.currentUser.bioLinks.slice();
|
|
D.visible = true;
|
|
}
|
|
|
|
function showPreviousInstancesUserDialog(userRef) {
|
|
const D = previousInstancesUserDialog.value;
|
|
D.userRef = userRef;
|
|
D.visible = true;
|
|
// trigger watcher
|
|
D.openFlg = true;
|
|
nextTick(() => (D.openFlg = false));
|
|
}
|
|
|
|
function timeToText(args) {
|
|
return utils.timeToText(args);
|
|
}
|
|
|
|
function toggleAvatarCopying() {
|
|
userRequest.saveCurrentUser({
|
|
allowAvatarCopying: !API.currentUser.allowAvatarCopying
|
|
});
|
|
// .then((args) => {
|
|
// return args;
|
|
// });
|
|
}
|
|
|
|
function resetHome() {
|
|
$confirm('Continue? Reset Home', 'Confirm', {
|
|
confirmButtonText: 'Confirm',
|
|
cancelButtonText: 'Cancel',
|
|
type: 'info',
|
|
callback: (action) => {
|
|
if (action === 'confirm') {
|
|
userRequest
|
|
.saveCurrentUser({
|
|
homeLocation: ''
|
|
})
|
|
.then((args) => {
|
|
$message({
|
|
message: 'Home world has been reset',
|
|
type: 'success'
|
|
});
|
|
return args;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function copyUserId(userId) {
|
|
copyToClipboard(userId, 'User ID copied to clipboard');
|
|
}
|
|
|
|
function copyUserURL(userId) {
|
|
copyToClipboard(`https://vrchat.com/home/user/${userId}`, 'User URL copied to clipboard');
|
|
}
|
|
|
|
function copyUserDisplayName(displayName) {
|
|
copyToClipboard(displayName, 'User DisplayName copied to clipboard');
|
|
}
|
|
|
|
async function setUserDialogGroupSorting(sortOrder) {
|
|
const D = props.userDialog;
|
|
if (D.groupSorting === sortOrder) {
|
|
return;
|
|
}
|
|
D.groupSorting = sortOrder;
|
|
await sortCurrentUserGroups();
|
|
}
|
|
|
|
async function exitEditModeCurrentUserGroups() {
|
|
userDialogGroupEditMode.value = false;
|
|
userDialogGroupEditGroups.value = [];
|
|
await sortCurrentUserGroups();
|
|
}
|
|
|
|
async function editModeCurrentUserGroups() {
|
|
await props.updateInGameGroupOrder();
|
|
userDialogGroupEditGroups.value = Array.from(API.currentUserGroups.values());
|
|
userDialogGroupEditGroups.value.sort(sortGroupsByInGame);
|
|
userDialogGroupEditMode.value = true;
|
|
}
|
|
|
|
async function saveInGameGroupOrder() {
|
|
userDialogGroupEditGroups.value.sort(sortGroupsByInGame);
|
|
try {
|
|
await AppApi.SetVRChatRegistryKey(
|
|
`VRC_GROUP_ORDER_${API.currentUser.id}`,
|
|
JSON.stringify(props.inGameGroupOrder),
|
|
3
|
|
);
|
|
} catch (err) {
|
|
console.error(err);
|
|
$message({
|
|
message: 'Failed to save in-game group order',
|
|
type: 'error'
|
|
});
|
|
}
|
|
}
|
|
|
|
function moveGroupUp(groupId) {
|
|
const index = props.inGameGroupOrder.indexOf(groupId);
|
|
if (index > 0) {
|
|
props.inGameGroupOrder.splice(index, 1);
|
|
props.inGameGroupOrder.splice(index - 1, 0, groupId);
|
|
saveInGameGroupOrder();
|
|
}
|
|
}
|
|
|
|
function moveGroupDown(groupId) {
|
|
const index = props.inGameGroupOrder.indexOf(groupId);
|
|
if (index < props.inGameGroupOrder.length - 1) {
|
|
props.inGameGroupOrder.splice(index, 1);
|
|
props.inGameGroupOrder.splice(index + 1, 0, groupId);
|
|
saveInGameGroupOrder();
|
|
}
|
|
}
|
|
|
|
function moveGroupTop(groupId) {
|
|
const index = props.inGameGroupOrder.indexOf(groupId);
|
|
if (index > 0) {
|
|
props.inGameGroupOrder.splice(index, 1);
|
|
props.inGameGroupOrder.unshift(groupId);
|
|
saveInGameGroupOrder();
|
|
}
|
|
}
|
|
|
|
function moveGroupBottom(groupId) {
|
|
const index = props.inGameGroupOrder.indexOf(groupId);
|
|
if (index < props.inGameGroupOrder.length - 1) {
|
|
props.inGameGroupOrder.splice(index, 1);
|
|
props.inGameGroupOrder.push(groupId);
|
|
saveInGameGroupOrder();
|
|
}
|
|
}
|
|
|
|
async function setUserDialogWorldSorting(sortOrder) {
|
|
const D = props.userDialog;
|
|
if (D.worldSorting === sortOrder) {
|
|
return;
|
|
}
|
|
D.worldSorting = sortOrder;
|
|
refreshUserDialogWorlds();
|
|
}
|
|
|
|
async function setUserDialogWorldOrder(order) {
|
|
const D = props.userDialog;
|
|
if (D.worldOrder === order) {
|
|
return;
|
|
}
|
|
D.worldOrder = order;
|
|
refreshUserDialogWorlds();
|
|
}
|
|
|
|
function changeUserDialogAvatarSorting(sortOption) {
|
|
const D = props.userDialog;
|
|
D.avatarSorting = sortOption;
|
|
sortUserDialogAvatars(D.avatars);
|
|
}
|
|
|
|
function refreshUserDialogTreeData() {
|
|
emit('refreshUserDialogTreeData');
|
|
}
|
|
function setGroupVisibility(groupId, visibility) {
|
|
emit('setGroupVisibility', groupId, visibility);
|
|
}
|
|
function refreshUserDialogAvatars() {
|
|
emit('refreshUserDialogAvatars');
|
|
}
|
|
function leaveGroupPrompt(id) {
|
|
emit('leaveGroupPrompt', id);
|
|
}
|
|
function sortUserDialogAvatars(avatars) {
|
|
emit('sortUserDialogAvatars', avatars);
|
|
}
|
|
function logout() {
|
|
emit('logout');
|
|
}
|
|
function showAvatarAuthorDialog(userId, authorId, currentAvatarImageUrl) {
|
|
emit('showAvatarAuthorDialog', userId, authorId, currentAvatarImageUrl);
|
|
}
|
|
function showGalleryDialog() {
|
|
emit('showGalleryDialog');
|
|
}
|
|
function saveCurrentUserGroups() {
|
|
emit('saveCurrentUserGroups');
|
|
}
|
|
function saveUserMemo(userId, memo) {
|
|
emit('saveUserMemo', userId, memo);
|
|
}
|
|
function clearInviteImageUpload() {
|
|
emit('clearInviteImageUpload');
|
|
}
|
|
function inviteImageUpload(event) {
|
|
emit('inviteImageUpload', event);
|
|
}
|
|
function refreshGalleryTable() {
|
|
emit('refreshGalleryTable');
|
|
}
|
|
function refreshVRCPlusIconsTable() {
|
|
emit('refreshVRCPlusIconsTable');
|
|
}
|
|
function refreshStickerTable() {
|
|
emit('refreshStickerTable');
|
|
}
|
|
function refreshEmojiTable() {
|
|
emit('refreshEmojiTable');
|
|
}
|
|
function refreshPrintTable() {
|
|
emit('refreshPrintTable');
|
|
}
|
|
</script>
|