bye profile tab

This commit is contained in:
pa
2025-10-26 19:09:27 +09:00
committed by Natsumi
parent d88e05db4e
commit 425a04d28b
8 changed files with 89 additions and 531 deletions

View File

@@ -1,398 +0,0 @@
<template>
<div class="x-container">
<div class="options-container" style="margin-top: 0">
<span class="header">{{ t('view.profile.profile.header') }}</span>
<div class="x-friend-list" style="margin-top: 10px">
<div class="x-friend-item" @click="showUserDialog(currentUser.id)">
<div class="avatar">
<img :src="userImage(currentUser, true)" loading="lazy" />
</div>
<div class="detail">
<span class="name" v-text="currentUser.displayName"></span>
<span class="extra" v-text="currentUser.username"></span>
</div>
</div>
<div class="x-friend-item" style="cursor: default">
<div class="detail">
<span class="name">{{ t('view.profile.profile.last_activity') }}</span>
<span class="extra">{{ formatDateFilter(currentUser.last_activity, 'long') }}</span>
</div>
</div>
<div class="x-friend-item" style="cursor: default">
<div class="detail">
<span class="name">{{ t('view.profile.profile.two_factor') }}</span>
<span class="extra">{{
currentUser.twoFactorAuthEnabled
? t('view.profile.profile.two_factor_enabled')
: t('view.profile.profile.two_factor_disabled')
}}</span>
</div>
</div>
<div class="x-friend-item" @click="getVRChatCredits()">
<div class="detail">
<span class="name">{{ t('view.profile.profile.vrchat_credits') }}</span>
<span class="extra">{{ vrchatCredit ?? t('view.profile.profile.refresh') }}</span>
</div>
</div>
</div>
<div style="margin-top: 10px">
<el-button
size="small"
type="danger"
plain
:icon="SwitchButton"
style="margin-left: 0; margin-right: 5px; margin-top: 10px"
@click="logout()"
>{{ t('view.profile.profile.logout') }}</el-button
>
<el-button
size="small"
:icon="Picture"
style="margin-left: 0; margin-right: 5px; margin-top: 10px"
@click="redirectToToolsTab"
>{{ t('view.profile.profile.manage_gallery_inventory_icon') }}</el-button
>
<el-button
size="small"
:icon="ChatDotRound"
style="margin-left: 0; margin-right: 5px; margin-top: 10px"
@click="redirectToToolsTab"
>{{ t('view.tools.export.discord_names') }}</el-button
>
<el-button
size="small"
:icon="Printer"
style="margin-left: 0; margin-right: 5px; margin-top: 10px"
@click="redirectToToolsTab"
>{{ t('view.tools.export.export_friend_list') }}</el-button
>
<el-button
size="small"
:icon="User"
style="margin-left: 0; margin-right: 5px; margin-top: 10px"
@click="redirectToToolsTab"
>{{ t('view.tools.export.export_own_avatars') }}</el-button
>
</div>
</div>
<div class="options-container">
<span class="header">{{ t('view.profile.game_info.header') }}</span>
<div class="x-friend-list" style="margin-top: 10px">
<div class="x-friend-item">
<div class="detail" @click="getVisits">
<span class="name">{{ t('view.profile.game_info.online_users') }}</span>
<span v-if="visits" class="extra">{{
t('view.profile.game_info.user_online', { count: visits })
}}</span>
<span v-else class="extra">{{ t('view.profile.game_info.refresh') }}</span>
</div>
</div>
</div>
</div>
<div class="options-container">
<div class="header-bar">
<span class="header">{{ t('view.profile.vrc_sdk_downloads.header') }}</span>
<el-tooltip placement="top" :content="t('view.profile.refresh_tooltip')">
<el-button
type="default"
size="small"
:icon="Refresh"
circle
style="margin-left: 5px"
@click="getConfig"></el-button>
</el-tooltip>
</div>
<div class="x-friend-list" style="margin-top: 10px">
<div
v-for="(link, item) in cachedConfig.downloadUrls"
:key="item"
class="x-friend-item"
placement="top">
<div class="detail" @click="openExternalLink(link)">
<span class="name" v-text="item"></span>
<span class="extra" v-text="link"></span>
</div>
</div>
</div>
</div>
<div class="options-container">
<span class="header">{{ t('view.profile.direct_access.header') }}</span>
<div style="margin-top: 10px">
<el-button-group>
<el-button size="small" @click="promptUsernameDialog()">{{
t('view.profile.direct_access.username')
}}</el-button>
<el-button size="small" @click="promptUserIdDialog()">{{
t('view.profile.direct_access.user_id')
}}</el-button>
<el-button size="small" @click="promptWorldDialog()">{{
t('view.profile.direct_access.world_instance')
}}</el-button>
<el-button size="small" @click="promptAvatarDialog()">{{
t('view.profile.direct_access.avatar')
}}</el-button>
</el-button-group>
</div>
</div>
<div class="options-container">
<span class="header">{{ t('view.profile.past_display_names') }}</span>
<DataTable v-bind="pastDisplayNameTable" style="margin-top: 10px">
<el-table-column
:label="t('table.profile.previous_display_name.date')"
prop="updated_at"
:sortable="true">
<template #default="scope">
<span>{{ formatDateFilter(scope.row.updated_at, 'long') }}</span>
</template>
</el-table-column>
<el-table-column
:label="t('table.profile.previous_display_name.name')"
prop="displayName"></el-table-column>
</DataTable>
</div>
<div class="options-container">
<div class="header-bar">
<span class="header">{{ t('view.profile.config_json') }}</span>
<el-tooltip placement="top" :content="t('view.profile.refresh_tooltip')">
<el-button
type="default"
size="small"
:icon="Refresh"
circle
style="margin-left: 5px"
@click="refreshConfigTreeData()"></el-button>
</el-tooltip>
<el-tooltip placement="top" :content="t('view.profile.clear_results_tooltip')">
<el-button
type="default"
size="small"
:icon="Delete"
circle
style="margin-left: 5px"
@click="configTreeData = []"></el-button>
</el-tooltip>
</div>
<el-tree v-if="configTreeData.length > 0" :data="configTreeData" style="margin-top: 10px; 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>
</div>
<div class="options-container">
<div class="header-bar">
<span class="header">{{ t('view.profile.feedback') }}</span>
<el-tooltip placement="top" :content="t('view.profile.refresh_tooltip')">
<el-button
type="default"
size="small"
:icon="Refresh"
circle
style="margin-left: 5px"
@click="getCurrentUserFeedback()"></el-button>
</el-tooltip>
<el-tooltip placement="top" :content="t('view.profile.clear_results_tooltip')">
<el-button
type="default"
size="small"
:icon="Delete"
circle
style="margin-left: 5px"
@click="currentUserFeedbackData = []"></el-button>
</el-tooltip>
</div>
<el-tree
v-if="currentUserFeedbackData.length > 0"
:data="currentUserFeedbackData"
style="margin-top: 10px; 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>
</div>
</div>
</template>
<script setup>
import { ChatDotRound, Delete, Edit, Picture, Printer, Refresh, SwitchButton, User } from '@element-plus/icons-vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import {
buildTreeData,
formatDateFilter,
openExternalLink,
parseAvatarUrl,
parseUserUrl,
userImage
} from '../../shared/utils';
import { useAvatarStore, useInviteStore, useSearchStore, useUiStore, useUserStore } from '../../stores';
import { authRequest, miscRequest, userRequest } from '../../api';
import { redirectToToolsTab } from '../../shared/utils/base/ui';
import { useAuthStore } from '../../stores';
const { pastDisplayNameTable, currentUser } = storeToRefs(useUserStore());
const { showUserDialog, lookupUser, getCurrentUser } = useUserStore();
const { showAvatarDialog } = useAvatarStore();
const { showEditInviteMessageDialog, refreshInviteMessageTableData } = useInviteStore();
const {
inviteMessageTable,
inviteResponseMessageTable,
inviteRequestMessageTable,
inviteRequestResponseMessageTable
} = storeToRefs(useInviteStore());
const { directAccessWorld } = useSearchStore();
const { logout } = useAuthStore();
const { cachedConfig } = storeToRefs(useAuthStore());
const { t } = useI18n();
const vrchatCredit = ref(null);
const configTreeData = ref([]);
const currentUserTreeData = ref([]);
const currentUserFeedbackData = ref([]);
const visits = ref(0);
function getVisits() {
miscRequest.getVisits().then((args) => {
visits.value = args.json;
});
}
function getVRChatCredits() {
miscRequest.getVRChatCredits().then((args) => (vrchatCredit.value = args.json?.balance));
}
function promptUsernameDialog() {
ElMessageBox.prompt(t('prompt.direct_access_username.description'), t('prompt.direct_access_username.header'), {
distinguishCancelAndClose: true,
confirmButtonText: t('prompt.direct_access_username.ok'),
cancelButtonText: t('prompt.direct_access_username.cancel'),
inputPattern: /\S+/,
inputErrorMessage: t('prompt.direct_access_username.input_error')
})
.then(({ value }) => {
if (value) {
lookupUser({
displayName: value
});
}
})
.catch(() => {});
}
function promptUserIdDialog() {
ElMessageBox.prompt(t('prompt.direct_access_user_id.description'), t('prompt.direct_access_user_id.header'), {
distinguishCancelAndClose: true,
confirmButtonText: t('prompt.direct_access_user_id.ok'),
cancelButtonText: t('prompt.direct_access_user_id.cancel'),
inputPattern: /\S+/,
inputErrorMessage: t('prompt.direct_access_user_id.input_error')
})
.then(({ value }) => {
if (value) {
const trimmedValue = value.trim();
const testUrl = trimmedValue.substring(0, 15);
if (testUrl === 'https://vrchat.') {
const userId = parseUserUrl(trimmedValue);
if (userId) {
showUserDialog(userId);
} else {
ElMessage({
message: t('prompt.direct_access_user_id.message.error'),
type: 'error'
});
}
} else {
showUserDialog(trimmedValue);
}
}
})
.catch(() => {});
}
function promptWorldDialog() {
ElMessageBox.prompt(t('prompt.direct_access_world_id.description'), t('prompt.direct_access_world_id.header'), {
distinguishCancelAndClose: true,
confirmButtonText: t('prompt.direct_access_world_id.ok'),
cancelButtonText: t('prompt.direct_access_world_id.cancel'),
inputPattern: /\S+/,
inputErrorMessage: t('prompt.direct_access_world_id.input_error')
})
.then(({ value }) => {
if (value) {
const trimmedValue = value.trim();
if (!directAccessWorld(trimmedValue)) {
ElMessage({
message: t('prompt.direct_access_world_id.message.error'),
type: 'error'
});
}
}
})
.catch(() => {});
}
function promptAvatarDialog() {
ElMessageBox.prompt(
t('prompt.direct_access_avatar_id.description'),
t('prompt.direct_access_avatar_id.header'),
{
distinguishCancelAndClose: true,
confirmButtonText: t('prompt.direct_access_avatar_id.ok'),
cancelButtonText: t('prompt.direct_access_avatar_id.cancel'),
inputPattern: /\S+/,
inputErrorMessage: t('prompt.direct_access_avatar_id.input_error')
}
)
.then(({ value }) => {
if (value) {
const trimmedValue = value.trim();
const testUrl = trimmedValue.substring(0, 15);
if (testUrl === 'https://vrchat.') {
const avatarId = parseAvatarUrl(trimmedValue);
if (avatarId) {
showAvatarDialog(avatarId);
} else {
ElMessage({
message: t('prompt.direct_access_avatar_id.message.error'),
type: 'error'
});
}
} else {
showAvatarDialog(trimmedValue);
}
}
})
.catch(() => {});
}
async function getConfig() {
await authRequest.getConfig();
}
async function refreshConfigTreeData() {
await getConfig();
configTreeData.value = buildTreeData(cachedConfig.value);
}
async function refreshCurrentUserTreeData() {
await getCurrentUser();
currentUserTreeData.value = buildTreeData(currentUser.value);
}
function getCurrentUserFeedback() {
userRequest.getUserFeedback({ userId: currentUser.value.id }).then((args) => {
if (args.params.userId === currentUser.value.id) {
currentUserFeedbackData.value = buildTreeData(args.json);
}
});
}
</script>

View File

@@ -1,86 +0,0 @@
<template>
<el-dialog
class="x-dialog"
:model-value="editInviteMessageDialog.visible"
:title="t('dialog.edit_invite_message.header')"
width="400px"
@close="closeDialog">
<div style="font-size: 12px">
<span>{{ t('dialog.edit_invite_message.description') }}</span>
<el-input
v-model="message"
type="textarea"
size="small"
maxlength="64"
show-word-limit
:autosize="{ minRows: 2, maxRows: 5 }"
placeholder=""
style="margin-top: 10px"></el-input>
</div>
<template #footer>
<el-button @click="closeDialog">{{ t('dialog.edit_invite_message.cancel') }}</el-button>
<el-button type="primary" @click="saveEditInviteMessage">{{
t('dialog.edit_invite_message.save')
}}</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { ref, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { inviteMessagesRequest } from '../../../api';
import { useInviteStore } from '../../../stores';
const { t } = useI18n();
const inviteStore = useInviteStore();
const { editInviteMessageDialog } = storeToRefs(inviteStore);
const message = ref('');
watch(
() => editInviteMessageDialog.value,
(newVal) => {
if (newVal && newVal.visible) {
message.value = newVal.newMessage;
}
},
{ deep: true }
);
function saveEditInviteMessage() {
const D = editInviteMessageDialog.value;
D.visible = false;
if (D.inviteMessage.message !== message.value) {
const slot = D.inviteMessage.slot;
const messageType = D.messageType;
const params = {
message: message.value
};
inviteMessagesRequest
.editInviteMessage(params, messageType, slot)
.catch((err) => {
throw err;
})
.then((args) => {
if (args.json[slot].message === D.inviteMessage.message) {
ElMessage({
message: "VRChat API didn't update message, try again",
type: 'error'
});
throw new Error("VRChat API didn't update message, try again");
} else {
ElMessage({ message: 'Invite message updated', type: 'success' });
}
return args;
});
}
}
function closeDialog() {
editInviteMessageDialog.value.visible = false;
}
</script>

View File

@@ -82,6 +82,20 @@
@change="setSentryErrorReporting()" />
</div>
</div>
<div class="options-container">
<span class="header">{{ t('view.profile.game_info.header') }}</span>
<div class="x-friend-list" style="margin-top: 10px">
<div class="x-friend-item">
<div class="detail" @click="getVisits">
<span class="name">{{ t('view.profile.game_info.online_users') }}</span>
<span v-if="visits" class="extra">{{
t('view.profile.game_info.user_online', { count: visits })
}}</span>
<span v-else class="extra">{{ t('view.profile.game_info.refresh') }}</span>
</div>
</div>
</div>
</div>
<div class="options-container">
<span class="header">{{ t('view.settings.advanced.advanced.remote_database.header') }}</span>
<simple-switch
@@ -336,6 +350,38 @@
</div>
</div>
<div class="options-container">
<div class="header-bar">
<span class="header">{{ t('view.profile.config_json') }}</span>
<el-tooltip placement="top" :content="t('view.profile.refresh_tooltip')">
<el-button
type="default"
size="small"
:icon="Refresh"
circle
style="margin-left: 5px"
@click="refreshConfigTreeData()"></el-button>
</el-tooltip>
<el-tooltip placement="top" :content="t('view.profile.clear_results_tooltip')">
<el-button
type="default"
size="small"
:icon="Delete"
circle
style="margin-left: 5px"
@click="configTreeData = []"></el-button>
</el-tooltip>
</div>
<el-tree v-if="configTreeData.length > 0" :data="configTreeData" style="margin-top: 10px; 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>
</div>
<RegistryBackupDialog />
<YouTubeApiDialog v-model:isYouTubeApiDialogVisible="isYouTubeApiDialogVisible" />
<TranslationApiDialog v-model:isTranslationApiDialogVisible="isTranslationApiDialogVisible" />
@@ -347,6 +393,7 @@
<script setup>
import {
CaretRight,
Delete,
DeleteFilled,
Folder,
Goods,
@@ -379,6 +426,8 @@
useVrcxStore,
useWorldStore
} from '../../../../stores';
import { authRequest, miscRequest } from '../../../../api';
import { buildTreeData } from '../../../../shared/utils/common';
import { openExternalLink } from '../../../../shared/utils';
import AvatarProviderDialog from '../../dialogs/AvatarProviderDialog.vue';
@@ -395,6 +444,7 @@
const { saveOpenVROption, updateVRLastLocation, updateOpenVR } = useVrStore();
const { showLaunchOptions } = useLaunchStore();
const { enablePrimaryPasswordChange } = useAuthStore();
const { cachedConfig } = storeToRefs(useAuthStore());
const { showConsole, clearVRCXCache, showRegistryBackupDialog } = useVrcxStore();
const { disableGameLogDialog } = useGameLogStore();
@@ -449,6 +499,8 @@
const isYouTubeApiDialogVisible = ref(false);
const isTranslationApiDialogVisible = ref(false);
const configTreeData = ref([]);
const visits = ref(0);
const cacheSize = reactive({
cachedUsers: 0,
@@ -547,4 +599,15 @@
advancedSettingsStore.setTranslationApi();
}
}
async function refreshConfigTreeData() {
await authRequest.getConfig();
configTreeData.value = buildTreeData(cachedConfig.value);
}
function getVisits() {
miscRequest.getVisits().then((args) => {
visits.value = args.json;
});
}
</script>

View File

@@ -40,19 +40,29 @@
</div>
</el-option>
</el-select>
<el-tooltip placement="bottom" :content="t('side_panel.direct_access_tooltip')">
<el-button type="default" size="small" :icon="Compass" circle @click="directAccessPaste"></el-button>
</el-tooltip>
<el-tooltip placement="bottom" :content="t('side_panel.refresh_tooltip')">
<el-button
type="default"
:loading="isRefreshFriendsLoading"
size="small"
:icon="Refresh"
circle
style="margin-right: 10px"
@click="refreshFriendsList" />
</el-tooltip>
<div>
<el-tooltip placement="bottom" :content="t('side_panel.refresh_tooltip')">
<el-button
type="default"
:loading="isRefreshFriendsLoading"
size="small"
:icon="Refresh"
circle
style="margin-right: 10px"
@click="refreshFriendsList"></el-button>
</el-tooltip>
<el-tooltip placement="bottom" :content="t('view.profile.profile.logout')">
<el-button
type="danger"
:icon="SwitchButton"
plain
size="small"
circle
@click="logout"
style="margin: 0 10px 0 0">
</el-button>
</el-tooltip>
</div>
</div>
<el-tabs class="zero-margin-tabs" stretch style="height: calc(100% - 60px); margin-top: 5px">
<el-tab-pane>
@@ -79,11 +89,11 @@
</template>
<script setup>
import { Compass, Refresh } from '@element-plus/icons-vue';
import { Refresh, SwitchButton } from '@element-plus/icons-vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { useFriendStore, useGroupStore, useSearchStore } from '../../stores';
import { useAuthStore, useFriendStore, useGroupStore, useSearchStore } from '../../stores';
import { userImage } from '../../shared/utils';
import FriendsSidebar from './components/FriendsSidebar.vue';
@@ -94,6 +104,7 @@
const { quickSearchRemoteMethod, quickSearchChange, directAccessPaste } = useSearchStore();
const { quickSearchItems } = storeToRefs(useSearchStore());
const { inGameGroupOrder, groupInstances } = storeToRefs(useGroupStore());
const { logout } = useAuthStore();
const { t } = useI18n();
</script>