replace some el-button

This commit is contained in:
pa
2026-01-11 16:01:43 +09:00
committed by Natsumi
parent 2043a321a4
commit 316d8ca9ba
24 changed files with 870 additions and 727 deletions

View File

@@ -4,7 +4,7 @@
<template #content>
<span>{{ t('dialog.user.info.last_join') }} <Timer :epoch="lastJoin" /></span>
</template>
<i class="ri-history-line"></i>
<i class="ri-map-pin-time-line text-muted-foreground"></i>
</TooltipWrapper>
</span>
</template>

View File

@@ -12,13 +12,12 @@
style="padding: 7px"></el-progress>
</div>
<div v-else-if="pendingVRCXUpdate || pendingVRCXInstall" class="pending-update">
<el-button
type="success"
plain
<Button
variant="outline"
style="font-size: 19px; height: 36px; width: 44px; margin: 10px"
@click="showVRCXUpdateDialog">
<i class="ri-download-line"></i>
</el-button>
</Button>
</div>
<el-menu ref="navMenuRef" class="nav-menu" :collapse="isCollapsed" :collapse-transition="false">
@@ -249,6 +248,7 @@
<script setup>
import { computed, defineAsyncComponent, onMounted, ref, watch } from 'vue';
import { ElMessageBox, dayjs } from 'element-plus';
import { Button } from '@/components/ui/button';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';

View File

@@ -179,57 +179,57 @@
v-text="avatarDialog.ref.description"></span>
</div>
</div>
<div style="flex: none; margin-left: 10px">
<div class="flex items-center">
<TooltipWrapper
v-if="avatarDialog.inCache"
side="top"
:content="t('dialog.avatar.actions.delete_cache_tooltip')">
<el-button
:icon="Delete"
size="large"
circle
<Button
class="rounded-full mr-2"
size="icon-lg"
variant="outline"
:disabled="isGameRunning && avatarDialog.cacheLocked"
@click="deleteVRChatCache(avatarDialog.ref)"></el-button>
@click="deleteVRChatCache(avatarDialog.ref)"
><Trash2
/></Button>
</TooltipWrapper>
<TooltipWrapper
v-if="avatarDialog.isFavorite"
side="top"
:content="t('dialog.avatar.actions.favorite_tooltip')">
<el-button
type="warning"
:icon="StarFilled"
size="large"
circle
style="margin-left: 5px"
@click="avatarDialogCommand('Add Favorite')"></el-button>
<Button class="rounded-full" size="icon-lg" @click="avatarDialogCommand('Add Favorite')"
><Star
/></Button>
</TooltipWrapper>
<TooltipWrapper v-else side="top" :content="t('dialog.avatar.actions.favorite_tooltip')">
<el-button
type="default"
:icon="Star"
size="large"
circle
style="margin-left: 5px"
@click="avatarDialogCommand('Add Favorite')"></el-button>
<Button
class="rounded-full"
size="icon-lg"
variant="outline"
@click="avatarDialogCommand('Add Favorite')"
><Star
/></Button>
</TooltipWrapper>
<TooltipWrapper side="top" :content="t('dialog.avatar.actions.select')">
<el-button
type="default"
:icon="Check"
size="large"
circle
<Button
class="rounded-full ml-2"
size="icon-lg"
variant="outline"
:disabled="currentUser.currentAvatar === avatarDialog.id"
style="margin-left: 5px"
@click="selectAvatarWithoutConfirmation(avatarDialog.id)"></el-button>
@click="selectAvatarWithoutConfirmation(avatarDialog.id)">
<CircleCheck
/></Button>
</TooltipWrapper>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<el-button
:type="avatarDialog.isBlocked ? 'danger' : 'default'"
:icon="MoreFilled"
size="large"
style="margin-left: 5px"
circle></el-button>
<Button
class="rounded-full ml-2"
:variant="avatarDialog.isBlocked ? 'destructive' : 'outline'"
size="icon-lg">
<Ellipsis />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem @click="avatarDialogCommand('Refresh')">
@@ -442,12 +442,13 @@
}}<TooltipWrapper side="top" :content="t('dialog.avatar.info.id_tooltip')">
<DropdownMenu>
<DropdownMenuTrigger as-child>
<el-button
type="default"
:icon="CopyDocument"
size="small"
circle
@click.stop></el-button>
<Button
class="rounded-full text-xs"
size="icon-sm"
variant="outline"
@click.stop
><i class="ri-file-copy-line"></i
></Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem @click="copyAvatarId(avatarDialog.id)">
@@ -511,19 +512,20 @@
</div>
</el-tab-pane>
<el-tab-pane name="JSON" :label="t('dialog.avatar.json.header')" style="max-height: 50vh" lazy>
<el-button
type="default"
size="small"
:icon="Refresh"
circle
@click="refreshAvatarDialogTreeData"></el-button>
<el-button
type="default"
size="small"
:icon="Download"
circle
style="margin-left: 5px"
@click="downloadAndSaveJson(avatarDialog.id, avatarDialog.ref)"></el-button>
<Button
class="rounded-full h-6 w-6 mr-2"
size="icon-sm"
variant="outline"
@click="refreshAvatarDialogTreeData()">
<RefreshCcw />
</Button>
<Button
class="rounded-full h-6 w-6"
size="icon-sm"
variant="outline"
@click="downloadAndSaveJson(avatarDialog.id, avatarDialog.ref)">
<Download />
</Button>
<vue-json-pretty :data="treeData" :deep="2" :theme="isDarkMode ? 'dark' : 'light'" show-icon />
<br />
<vue-json-pretty
@@ -547,26 +549,20 @@
<script setup>
import {
Back,
Check,
CircleCheck,
CircleClose,
CopyDocument,
Delete,
Download,
Edit,
MoreFilled,
Picture,
Refresh,
Right,
Share,
Star,
StarFilled,
Upload,
User,
Warning
} from '@element-plus/icons-vue';
import { CircleCheck, Ellipsis, RefreshCcw, Star, Trash2 } from 'lucide-vue-next';
import { computed, defineAsyncComponent, nextTick, ref, watch } from 'vue';
import { Button } from '@/components/ui/button';
import { ElMessageBox } from 'element-plus';
import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner';

View File

@@ -35,14 +35,14 @@
<br />
<template
v-if="setAvatarTagsDialog.ownAvatars.length === props.setAvatarTagsDialog.selectedAvatarIds.length">
<el-button size="small" @click="setAvatarTagsSelectToggle">{{
<Button size="sm" variant="outline" @click="setAvatarTagsSelectToggle">{{
t('dialog.set_avatar_tags.select_none')
}}</el-button>
}}</Button>
</template>
<template v-else>
<el-button size="small" @click="setAvatarTagsSelectToggle">{{
<Button size="sm" variant="outline" @click="setAvatarTagsSelectToggle">{{
t('dialog.set_avatar_tags.select_all')
}}</el-button>
}}</Button>
</template>
<span style="margin-left: 5px"
>{{ props.setAvatarTagsDialog.selectedAvatarIds.length }} /
@@ -76,24 +76,25 @@
<span v-else class="extra" v-text="avatar.releaseStatus"></span>
<span class="extra" v-text="avatarTagStrings.get(avatar.id)"></span>
</div>
<el-button text size="small" style="margin-left: 5px" @click.stop>
<Button size="sm" variant="ghost" style="margin-left: 5px" @click.stop>
<el-checkbox
:model-value="props.setAvatarTagsDialog.selectedAvatarIds.includes(avatar.id)"
@click="toggleAvatarSelection(avatar.id)"></el-checkbox>
</el-button>
</Button>
</div>
</div>
</template>
<template #footer>
<el-button @click="closeSetAvatarTagsDialog">{{ t('dialog.set_avatar_tags.cancel') }}</el-button>
<el-button type="primary" @click="saveSetAvatarTagsDialog">{{
t('dialog.set_avatar_tags.save')
}}</el-button>
<Button variant="secondary" @click="closeSetAvatarTagsDialog">{{
t('dialog.set_avatar_tags.cancel')
}}</Button>
<Button @click="saveSetAvatarTagsDialog">{{ t('dialog.set_avatar_tags.save') }}</Button>
</template>
</el-dialog>
</template>
<script setup>
import { Button } from '@/components/ui/button';
import { Loading } from '@element-plus/icons-vue';
import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';

View File

@@ -357,12 +357,13 @@
<Location :location="room.tag" style="display: inline-block" />
<InviteYourself :location="room.tag" style="margin-left: 5px" />
<TooltipWrapper side="top" content="Refresh player count">
<el-button
size="small"
:icon="Refresh"
style="margin-left: 5px"
circle
@click="refreshInstancePlayerCount(room.tag)" />
<Button
class="rounded-full w-6 h-6 text-xs text-muted-foreground hover:text-foreground"
size="icon"
variant="outline"
@click="refreshInstancePlayerCount(room.tag)"
><i class="ri-refresh-line"></i
></Button>
</TooltipWrapper>
<LastJoin :location="room.tag" :currentlocation="lastLocation.location" />
<InstanceInfo
@@ -485,22 +486,20 @@
</TooltipWrapper>
<template v-if="hasGroupPermission(groupDialog.ref, 'group-announcement-manage')">
<TooltipWrapper side="top" :content="t('dialog.group.posts.edit_tooltip')">
<el-button
text
:icon="Edit"
size="small"
<Button
size="sm"
variant="ghost"
style="margin-left: 5px; padding: 0"
@click="
showGroupPostEditDialog(groupDialog.id, groupDialog.announcement)
" />
"></Button>
</TooltipWrapper>
<TooltipWrapper side="top" :content="t('dialog.group.posts.delete_tooltip')">
<el-button
text
:icon="Delete"
size="small"
<Button
size="sm"
variant="ghost"
style="margin-left: 5px; padding: 0"
@click="confirmDeleteGroupPost(groupDialog.announcement)" />
@click="confirmDeleteGroupPost(groupDialog.announcement)"></Button>
</TooltipWrapper>
</template>
</div>
@@ -621,13 +620,12 @@
<span class="extra"
>{{ groupDialog.ref.$url }}
<TooltipWrapper side="top" :content="t('dialog.group.info.url_tooltip')">
<el-button
type="default"
size="small"
:icon="CopyDocument"
circle
<Button
class="rounded-full"
size="icon"
variant="outline"
style="margin-left: 5px"
@click="copyToClipboard(groupDialog.ref.$url)" /> </TooltipWrapper
@click="copyToClipboard(groupDialog.ref.$url)"></Button> </TooltipWrapper
></span>
</div>
</div>
@@ -637,13 +635,12 @@
<span class="extra"
>{{ groupDialog.id }}
<TooltipWrapper side="top" :content="t('dialog.group.info.id_tooltip')">
<el-button
type="default"
size="small"
:icon="CopyDocument"
circle
<Button
class="rounded-full"
size="icon"
variant="outline"
style="margin-left: 5px"
@click="copyToClipboard(groupDialog.id)" /> </TooltipWrapper
@click="copyToClipboard(groupDialog.id)"></Button> </TooltipWrapper
></span>
</div>
</div>
@@ -797,22 +794,24 @@
<template
v-if="hasGroupPermission(groupDialog.ref, 'group-announcement-manage')">
<TooltipWrapper side="top" :content="t('dialog.group.posts.edit_tooltip')">
<el-button
text
:icon="Edit"
size="small"
style="margin-left: 5px"
@click="showGroupPostEditDialog(groupDialog.id, post)" />
<Button
size="icon-sm"
class="h-6 w-6 text-xs text-muted-foreground hover:text-foreground"
variant="ghost"
@click="showGroupPostEditDialog(groupDialog.id, post)"
><i class="ri-pencil-line"></i
></Button>
</TooltipWrapper>
<TooltipWrapper
side="top"
:content="t('dialog.group.posts.delete_tooltip')">
<el-button
text
:icon="Delete"
size="small"
style="margin-left: 5px"
@click="confirmDeleteGroupPost(post)" />
<Button
size="icon-sm"
class="h-6 w-6 text-xs text-muted-foreground hover:text-foreground"
variant="ghost"
@click="confirmDeleteGroupPost(post)"
><i class="ri-delete-bin-line"></i
></Button>
</TooltipWrapper>
</template>
</div>
@@ -832,20 +831,23 @@
t('dialog.group.members.friends_only')
}}</span>
<div style="margin-top: 10px">
<el-button
type="default"
size="small"
:icon="Refresh"
<Button
class="rounded-full h-6 w-6"
variant="outline"
size="icon-sm"
:loading="isGroupMembersLoading"
circle
@click="loadAllGroupMembers" />
<el-button
type="default"
size="small"
:icon="Download"
circle
@click="loadAllGroupMembers">
<Spinner v-if="isGroupMembersLoading" /><RefreshCcw v-else
/></Button>
<Button
class="rounded-full h-6 w-6 ml-2"
size="icon-sm"
variant="outline"
style="margin-left: 5px"
@click="downloadAndSaveJson(`${groupDialog.id}_members`, groupDialog.members)" />
@click="downloadAndSaveJson(`${groupDialog.id}_members`, groupDialog.members)">
<Download class="h-4 w-4" />
</Button>
<span
v-if="groupDialog.memberSearch.length"
style="font-size: 14px; margin-left: 5px; margin-right: 5px"
@@ -856,62 +858,41 @@
>
<div
v-if="hasGroupPermission(groupDialog.ref, 'group-members-manage')"
style="float: right">
style="float: right"
class="flex items-center">
<span style="margin-right: 5px">{{ t('dialog.group.members.sort_by') }}</span>
<DropdownMenu>
<DropdownMenuTrigger
as-child
:disabled="isGroupMembersLoading || groupDialog.memberSearch.length > 0">
<el-button
size="small"
:disabled="isGroupMembersLoading || groupDialog.memberSearch.length > 0"
@click.stop>
<span>
{{ t(groupDialog.memberSortOrder.name) }}
<el-icon style="margin-left: 5px"><ArrowDown /></el-icon>
</span>
</el-button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
<Select
v-model="groupDialogMemberSortValue"
:disabled="isGroupMembersLoading || groupDialog.memberSearch.length > 0">
<SelectTrigger class="h-8 w-45 mr-1">
<SelectValue :placeholder="t('dialog.group.members.sort_by')" />
</SelectTrigger>
<SelectContent>
<SelectItem
v-for="item in groupDialogSortingOptions"
:key="item.name"
@click="setGroupMemberSortOrder(item)">
:key="item.value"
:value="item.value">
{{ t(item.name) }}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<span style="margin-right: 5px">{{ t('dialog.group.members.filter') }}</span>
<DropdownMenu>
<DropdownMenuTrigger
as-child
:disabled="isGroupMembersLoading || groupDialog.memberSearch.length > 0">
<el-button
size="small"
:disabled="isGroupMembersLoading || groupDialog.memberSearch.length > 0"
@click.stop>
<span>
{{ t(groupDialog.memberFilter.name) }}
<el-icon style="margin-left: 5px"><ArrowDown /></el-icon>
</SelectItem>
</SelectContent>
</Select>
<span class="ml-2 mr-1">{{ t('dialog.group.members.filter') }}</span>
<div style="display: inline-block; width: 220px">
<VirtualCombobox
v-model="groupDialogMemberFilterKey"
:groups="groupDialogMemberFilterGroups"
:disabled="isGroupMembersLoading || groupDialog.memberSearch.length > 0"
:placeholder="t('dialog.group.members.filter')"
:search-placeholder="t('dialog.group.members.search')"
:clearable="false"
:close-on-select="true">
<template #trigger="{ text }">
<span class="truncate">
{{ text || t('dialog.group.members.filter') }}
</span>
</el-button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
v-for="item in groupDialogFilterOptions"
:key="item.name"
@click="setGroupMemberFilter(item)">
{{ t(item.name) }}
</DropdownMenuItem>
<template v-for="role in groupDialog.ref.roles" :key="role.name">
<DropdownMenuItem
v-if="!role.defaultRole"
@click="setGroupMemberFilter(role)">
{{ role.name }}
</DropdownMenuItem>
</template>
</DropdownMenuContent>
</DropdownMenu>
</VirtualCombobox>
</div>
</div>
<el-input
v-model="groupDialog.memberSearch"
@@ -1107,19 +1088,20 @@
</el-tabs>
</el-tab-pane>
<el-tab-pane name="JSON" :label="t('dialog.group.json.header')" lazy>
<el-button
type="default"
size="small"
:icon="Refresh"
circle
@click="refreshGroupDialogTreeData()" />
<el-button
type="default"
size="small"
:icon="Download"
circle
style="margin-left: 5px"
@click="downloadAndSaveJson(groupDialog.id, groupDialog.ref)" />
<Button
class="rounded-full h-6 w-6 mr-2"
size="icon-sm"
variant="outline"
@click="refreshGroupDialogTreeData()">
<RefreshCcw />
</Button>
<Button
class="rounded-full h-6 w-6"
size="icon-sm"
variant="outline"
@click="downloadAndSaveJson(groupDialog.id, groupDialog.ref)">
<Download />
</Button>
<vue-json-pretty
:data="groupDialog.treeData"
:deep="2"
@@ -1137,7 +1119,6 @@
<script setup>
import {
ArrowDown,
Bell,
ChatLineSquare,
Check,
@@ -1145,7 +1126,6 @@
CircleClose,
Close,
CollectionTag,
CopyDocument,
Delete,
Download,
Edit,
@@ -1162,8 +1142,13 @@
View,
Warning
} from '@element-plus/icons-vue';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { computed, nextTick, reactive, ref, watch } from 'vue';
import { Button } from '@/components/ui/button';
import { ElMessageBox } from 'element-plus';
import { RefreshCcw } from 'lucide-vue-next';
import { Spinner } from '@/components/ui/spinner';
import { VirtualCombobox } from '@/components/ui/virtual-combobox';
import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
@@ -1240,6 +1225,78 @@
const groupDialogGalleryCurrentName = ref('0');
const groupDialogTabCurrentName = ref('0');
const isGroupGalleryLoading = ref(false);
const groupDialogMemberSortValue = computed({
get() {
return groupDialog.value?.memberSortOrder?.value ?? '';
},
set(value) {
const option = Object.values(groupDialogSortingOptions).find((item) => item.value === value);
if (option) {
setGroupMemberSortOrder(option);
}
}
});
const groupDialogMemberFilterKey = computed({
get() {
const filter = groupDialog.value?.memberFilter;
if (!filter) return null;
if (filter.id === null) return 'everyone';
if (filter.id === '') return 'usersWithNoRole';
return `role:${filter.id}`;
},
set(key) {
if (!key) return;
if (key === 'everyone') {
setGroupMemberFilter(groupDialogFilterOptions.everyone);
return;
}
if (key === 'usersWithNoRole') {
setGroupMemberFilter(groupDialogFilterOptions.usersWithNoRole);
return;
}
if (key.startsWith('role:')) {
const roleId = key.slice('role:'.length);
const role = groupDialog.value?.ref?.roles?.find((r) => r.id === roleId);
if (role) {
setGroupMemberFilter(role);
}
}
}
});
const groupDialogMemberFilterGroups = computed(() => {
const filterItems = Object.values(groupDialogFilterOptions).map((item) => ({
value: item.id === null ? 'everyone' : item.id === '' ? 'usersWithNoRole' : `role:${item.id}`,
label: t(item.name),
search: t(item.name)
}));
const roleItems = (groupDialog.value?.ref?.roles ?? [])
.filter((role) => !role.defaultRole)
.map((role) => ({
value: `role:${role.id}`,
label: role.name,
search: role.name
}));
return [
{
key: 'filters',
label: t('dialog.group.members.filter'),
items: filterItems
},
{
key: 'roles',
label: 'Roles',
items: roleItems
}
].filter((group) => group.items.length);
});
const selectedGalleryFile = ref({
selectedFileId: '',
selectedImageUrl: ''

View File

@@ -75,29 +75,33 @@
style="flex: none; width: 60px; height: 60px; border-radius: 4px; object-fit: cover"
@click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)"
loading="lazy" />
<el-button size="small" style="vertical-align: top" @click="clearImageGallerySelect">
<Button
size="sm"
variant="outline"
style="vertical-align: top"
@click="clearImageGallerySelect">
{{ t('dialog.invite_message.clear_selected_image') }}
</el-button>
</Button>
</div>
</template>
<template v-else>
<el-button size="small" style="margin-right: 5px" @click="showGallerySelectDialog">
<Button size="sm" variant="outline" @click="showGallerySelectDialog">
{{ t('dialog.invite_message.select_image') }}
</el-button>
</Button>
</template>
</el-form-item>
</el-form>
</div>
<template #footer>
<el-button @click="groupPostEditDialog.visible = false">
<Button variant="secondary" @click="groupPostEditDialog.visible = false">
{{ t('dialog.group_post_edit.cancel') }}
</el-button>
<el-button v-if="groupPostEditDialog.postId" @click="editGroupPost">
</Button>
<Button v-if="groupPostEditDialog.postId" @click="editGroupPost">
{{ t('dialog.group_post_edit.edit_post') }}
</el-button>
<el-button v-else @click="createGroupPost">
</Button>
<Button v-else @click="createGroupPost">
{{ t('dialog.group_post_edit.create_post') }}
</el-button>
</Button>
</template>
<GallerySelectDialog
:gallery-select-dialog="gallerySelectDialog"
@@ -108,6 +112,7 @@
<script setup>
import { computed, ref } from 'vue';
import { Button } from '@/components/ui/button';
import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';

View File

@@ -11,12 +11,12 @@
</div>
<template #footer>
<el-button type="default" @click="cancelInviteConfirm">
<Button variant="secondary" @click="cancelInviteConfirm">
{{ t('dialog.invite_message.cancel') }}
</el-button>
<el-button type="primary" @click="sendInviteConfirm">
</Button>
<Button @click="sendInviteConfirm">
{{ t('dialog.invite_message.confirm') }}
</el-button>
</Button>
</template>
</el-dialog>
</template>

View File

@@ -366,73 +366,74 @@
</el-tabs>
<template v-if="newInstanceDialog.selectedTab === 'Normal'" #footer>
<template v-if="newInstanceDialog.instanceCreated">
<el-button @click="copyInstanceUrl(newInstanceDialog.location)">{{
<Button variant="outline" class="mr-2" @click="copyInstanceUrl(newInstanceDialog.location)">{{
t('dialog.new_instance.copy_url')
}}</el-button>
<el-button @click="selfInvite(newInstanceDialog.location)">{{
}}</Button>
<Button variant="outline" class="mr-2" @click="selfInvite(newInstanceDialog.location)">{{
t('dialog.new_instance.self_invite')
}}</el-button>
<el-button
}}</Button>
<Button
variant="outline"
class="mr-2"
:disabled="
(newInstanceDialog.accessType === 'friends' || newInstanceDialog.accessType === 'invite') &&
newInstanceDialog.userId !== currentUser.id
"
@click="showInviteDialog(newInstanceDialog.location)"
>{{ t('dialog.new_instance.invite') }}</el-button
>{{ t('dialog.new_instance.invite') }}</Button
>
<template v-if="canOpenInstanceInGame">
<el-button @click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)">{{
t('dialog.new_instance.launch')
}}</el-button>
<el-button @click="handleAttachGame(newInstanceDialog.location, newInstanceDialog.shortName)">
<Button
variant="secondary"
class="mr-2"
@click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)"
>{{ t('dialog.new_instance.launch') }}</Button
>
<Button @click="handleAttachGame(newInstanceDialog.location, newInstanceDialog.shortName)">
{{ t('dialog.new_instance.open_ingame') }}
</el-button>
</Button>
</template>
<template v-else>
<el-button
type="primary"
@click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)"
>{{ t('dialog.new_instance.launch') }}</el-button
>
<Button @click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)">{{
t('dialog.new_instance.launch')
}}</Button>
</template>
</template>
<template v-else>
<el-button type="primary" @click="handleCreateNewInstance">{{
t('dialog.new_instance.create_instance')
}}</el-button>
<Button @click="handleCreateNewInstance">{{ t('dialog.new_instance.create_instance') }}</Button>
</template>
</template>
<template v-else-if="newInstanceDialog.selectedTab === 'Legacy'" #footer>
<el-button @click="copyInstanceUrl(newInstanceDialog.location)">{{
<Button variant="outline" class="mr-2" @click="copyInstanceUrl(newInstanceDialog.location)">{{
t('dialog.new_instance.copy_url')
}}</el-button>
<el-button @click="selfInvite(newInstanceDialog.location)">{{
}}</Button>
<Button variant="outline" class="mr-2" @click="selfInvite(newInstanceDialog.location)">{{
t('dialog.new_instance.self_invite')
}}</el-button>
<el-button
}}</Button>
<Button
variant="outline"
:disabled="
(newInstanceDialog.accessType === 'friends' || newInstanceDialog.accessType === 'invite') &&
newInstanceDialog.userId !== currentUser.id
"
@click="showInviteDialog(newInstanceDialog.location)"
>{{ t('dialog.new_instance.invite') }}</el-button
>{{ t('dialog.new_instance.invite') }}</Button
>
<template v-if="canOpenInstanceInGame">
<el-button @click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)">{{
t('dialog.new_instance.launch')
}}</el-button>
<el-button
type="primary"
@click="handleAttachGame(newInstanceDialog.location, newInstanceDialog.shortName)">
<Button
variant="secondary"
class="mr-2"
@click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)"
>{{ t('dialog.new_instance.launch') }}</Button
>
<Button @click="handleAttachGame(newInstanceDialog.location, newInstanceDialog.shortName)">
{{ t('dialog.new_instance.open_ingame') }}
</el-button>
</Button>
</template>
<template v-else>
<el-button
type="primary"
@click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)"
>{{ t('dialog.new_instance.launch') }}</el-button
>
<Button @click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)">{{
t('dialog.new_instance.launch')
}}</Button>
</template>
</template>
<InviteDialog :invite-dialog="inviteDialog" @closeInviteDialog="closeInviteDialog" />
@@ -441,6 +442,7 @@
<script setup>
import { computed, nextTick, ref, watch } from 'vue';
import { Button } from '@/components/ui/button';
import { Check as CheckIcon } from 'lucide-vue-next';
import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner';
@@ -467,7 +469,6 @@
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
import { groupRequest, instanceRequest, worldRequest } from '../../api';
import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group';
import { Button } from '../ui/button';
import { VirtualCombobox } from '../ui/virtual-combobox';
import { getNextDialogIndex } from '../../shared/utils/base/ui';

View File

@@ -28,26 +28,31 @@
">
<div style="flex: none">
<template v-if="isRealInstance(userDialog.$location.tag)">
<Launch :location="userDialog.$location.tag" />
<InviteYourself
:location="userDialog.$location.tag"
:shortname="userDialog.$location.shortName"
style="margin-left: 5px" />
<TooltipWrapper side="top" :content="t('dialog.user.info.refresh_instance_info')"
><el-button
size="small"
:icon="Refresh"
style="margin-left: 5px"
circle
@click="refreshInstancePlayerCount(userDialog.$location.tag)"></el-button>
</TooltipWrapper>
<LastJoin
:location="userDialog.$location.tag"
:currentlocation="lastLocation.location" />
<InstanceInfo
:location="userDialog.$location.tag"
:instance="userDialog.instance.ref"
:friendcount="userDialog.instance.friendCount" />
<div class="flex items-center mb-1">
<Launch :location="userDialog.$location.tag" />
<InviteYourself
:location="userDialog.$location.tag"
:shortname="userDialog.$location.shortName"
style="margin-left: 5px" />
<TooltipWrapper
side="top"
:content="t('dialog.user.info.refresh_instance_info')"
><Button
class="rounded-full w-6 h-6 text-xs text-muted-foreground hover:text-foreground"
size="icon"
variant="outline"
@click="refreshInstancePlayerCount(userDialog.$location.tag)"
><i class="ri-refresh-line"></i
></Button>
</TooltipWrapper>
<LastJoin
:location="userDialog.$location.tag"
:currentlocation="lastLocation.location" />
<InstanceInfo
:location="userDialog.$location.tag"
:instance="userDialog.instance.ref"
:friendcount="userDialog.instance.friendCount" />
</div>
</template>
<Location
:location="userDialog.ref.location"
@@ -234,23 +239,24 @@
>{{ bioCache.translated || userDialog.ref.bio || '-' }}</pre
>
<div style="float: right">
<el-button
<Button
v-if="translationApi && userDialog.ref.bio"
text
size="small"
:loading="translateLoading"
:disabled="translateLoading"
style="margin-left: 5px; padding: 0"
@click="translateBio"
><i class="ri-translate-2"></i
></el-button>
<el-button
class="w-3 h-6 text-xs mr-0.5"
size="icon-sm"
variant="ghost"
@click="translateBio">
<Spinner v-if="translateLoading" class="size-1" />
<i v-else class="ri-translate-2"> </i
></Button>
<Button
class="w-3 h-6 text-xs"
size="icon-sm"
variant="ghost"
v-if="userDialog.id === currentUser.id"
text
:icon="Edit"
size="small"
style="margin-left: 5px; padding: 0"
@click="showBioDialog"></el-button>
@click="showBioDialog"
><i class="ri-pencil-line"></i
></Button>
</div>
<div style="margin-top: 5px" class="flex items-center">
<TooltipWrapper v-for="(link, index) in userDialog.ref.bioLinks" :key="index">
@@ -496,13 +502,14 @@
<span class="name">{{ t('dialog.user.info.home_location') }}</span>
<span class="extra">
<span v-text="userDialog.$homeLocationName"></span>
<el-button
size="small"
:icon="Delete"
circle
<Button
class="rounded-full text-xs ml-1"
size="icon-sm"
variant="outline"
style="margin-left: 5px"
@click.stop="resetHome()">
</el-button>
@click.stop="resetHome()"
><i class="ri-delete-bin-line"></i>
</Button>
</span>
</div>
</div>
@@ -514,12 +521,13 @@
<TooltipWrapper side="top" :content="t('dialog.user.info.id_tooltip')">
<DropdownMenu>
<DropdownMenuTrigger as-child>
<el-button
type="default"
:icon="CopyDocument"
size="small"
circle
@click.stop></el-button>
<Button
class="rounded-full text-xs"
size="icon-sm"
variant="outline"
@click.stop
><i class="ri-file-copy-line"></i
></Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem @click="copyUserId(userDialog.id)">
@@ -562,24 +570,22 @@
</div>
<div style="display: flex; align-items: center">
<span style="margin-right: 5px">{{ t('dialog.user.groups.sort_by') }}</span>
<DropdownMenu>
<DropdownMenuTrigger as-child :disabled="userDialog.isMutualFriendsLoading">
<el-button size="small" :disabled="userDialog.isMutualFriendsLoading" @click.stop>
<span>
{{ t(userDialog.mutualFriendSorting.name) }}
<el-icon style="margin-left: 5px"><ArrowDown /></el-icon>
</span>
</el-button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
<Select
:model-value="userDialogMutualFriendSortingKey"
:disabled="userDialog.isMutualFriendsLoading"
@update:modelValue="setUserDialogMutualFriendSortingByKey">
<SelectTrigger size="sm" @click.stop>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem
v-for="(item, key) in userDialogMutualFriendSortingOptions"
:key="key"
@click="setUserDialogMutualFriendSorting(item)">
:key="String(key)"
:value="String(key)">
{{ t(item.name) }}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div
@@ -641,96 +647,80 @@
<div style="display: flex; align-items: center">
<template v-if="!userDialogGroupEditMode">
<span style="margin-right: 5px">{{ t('dialog.user.groups.sort_by') }}</span>
<DropdownMenu>
<DropdownMenuTrigger as-child :disabled="userDialog.isGroupsLoading">
<el-button size="small" :disabled="userDialog.isGroupsLoading" @click.stop>
<span>
{{ t(userDialog.groupSorting.name) }}
<el-icon style="margin-left: 5px"><ArrowDown /></el-icon>
</span>
</el-button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
<Select
:model-value="userDialogGroupSortingKey"
:disabled="userDialog.isGroupsLoading"
@update:modelValue="setUserDialogGroupSortingByKey">
<SelectTrigger size="sm" @click.stop>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem
v-for="(item, key) in userDialogGroupSortingOptions"
:key="key"
:key="String(key)"
:value="String(key)"
:disabled="
item === userDialogGroupSortingOptions.inGame &&
userDialog.id !== currentUser.id
"
@click="setUserDialogGroupSorting(item)">
">
{{ t(item.name) }}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SelectItem>
</SelectContent>
</Select>
</template>
<el-button
<Button
variant="outline"
size="sm"
v-if="userDialogGroupEditMode"
size="small"
:icon="Edit"
style="margin-right: 5px; height: 29px; padding: 7px 15px"
@click="exitEditModeCurrentUserGroups">
{{ t('dialog.user.groups.exit_edit_mode') }}
</el-button>
<el-button
</Button>
<Button
size="sm"
variant="outline"
v-else-if="currentUser.id === userDialog.id"
size="small"
:icon="Edit"
style="margin-right: 5px; height: 29px; padding: 7px 15px"
class="ml-2"
@click="editModeCurrentUserGroups">
{{ t('dialog.user.groups.edit_mode') }}
</el-button>
</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">
<!-- Bulk actions dropdown (shown only in edit mode) -->
<DropdownMenu>
<DropdownMenuTrigger as-child>
<el-button
size="small"
:icon="Setting"
style="
margin-right: 5px;
height: 29px;
padding: 7px 15px;
margin-bottom: 5px;
">
{{ t('dialog.group.actions.manage_selected') }}
<el-icon style="margin-left: 5px"><ArrowDown /></el-icon>
</el-button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem @click="bulkSetVisibility('visible')">
<Select :model-value="bulkGroupActionValue" @update:modelValue="handleBulkGroupAction">
<SelectTrigger size="sm" style="margin-right: 5px; margin-bottom: 5px" @click.stop>
<SelectValue :placeholder="t('dialog.group.actions.manage_selected')" />
</SelectTrigger>
<SelectContent>
<SelectItem value="visibility:visible">
{{ t('dialog.group.actions.visibility_everyone') }}
</DropdownMenuItem>
<DropdownMenuItem @click="bulkSetVisibility('friends')">
</SelectItem>
<SelectItem value="visibility:friends">
{{ t('dialog.group.actions.visibility_friends') }}
</DropdownMenuItem>
<DropdownMenuItem @click="bulkSetVisibility('hidden')">
</SelectItem>
<SelectItem value="visibility:hidden">
{{ t('dialog.group.actions.visibility_hidden') }}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive" @click="bulkLeaveGroups">
<Delete class="size-4" />
</SelectItem>
<SelectItem value="leave">
{{ t('dialog.user.groups.leave_group_tooltip') }}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SelectItem>
</SelectContent>
</Select>
<!-- Select All button -->
<el-button
size="small"
:icon="userDialogGroupAllSelected ? Close : Check"
style="height: 29px; padding: 7px 15px; margin-bottom: 5px"
<Button
size="sm"
variant="outline"
style="padding: 7px 15px; margin-bottom: 5px"
@click="selectAllGroups">
{{
userDialogGroupAllSelected
? t('dialog.group.actions.deselect_all')
: t('dialog.group.actions.select_all')
}}
</el-button>
</Button>
<div
v-for="group in userDialogGroupEditGroups"
@@ -755,7 +745,7 @@
<div style="margin-right: 3px; margin-left: 5px" @click.stop>
<el-button
size="small"
:icon="Download"
:icon="DownloadIcon"
style="
display: block;
padding: 7px;
@@ -767,7 +757,7 @@
</el-button>
<el-button
size="small"
:icon="Download"
:icon="DownloadIcon"
style="display: block; padding: 7px; font-size: 9px; margin-left: 0"
@click="moveGroupBottom(group.id)">
</el-button>
@@ -810,43 +800,40 @@
<span>({{ group.memberCount }})</span>
</span>
</div>
<DropdownMenu v-if="group.myMember?.visibility">
<DropdownMenuTrigger as-child :disabled="group.privacy !== 'default'">
<el-button :disabled="group.privacy !== 'default'" @click.stop size="small">
<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>
<el-icon style="margin-left: 5px"><ArrowDown /></el-icon>
</el-button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem @click="setGroupVisibility(group.id, 'visible')">
<Check v-if="group.myMember.visibility === 'visible'" class="size-4" />
<Select
v-if="group.myMember?.visibility"
:model-value="group.myMember.visibility"
:disabled="group.privacy !== 'default'"
@update:modelValue="(value) => setGroupVisibility(group.id, value)">
<SelectTrigger size="sm" @click.stop>
<SelectValue
:placeholder="
group.myMember.visibility === 'visible'
? t('dialog.group.tags.visible')
: group.myMember.visibility === 'friends'
? t('dialog.group.tags.friends')
: group.myMember.visibility === 'hidden'
? t('dialog.group.tags.hidden')
: group.myMember.visibility
" />
</SelectTrigger>
<SelectContent>
<SelectItem value="visible">
{{ t('dialog.group.actions.visibility_everyone') }}
</DropdownMenuItem>
<DropdownMenuItem @click="setGroupVisibility(group.id, 'friends')">
<Check v-if="group.myMember.visibility === 'friends'" class="size-4" />
</SelectItem>
<SelectItem value="friends">
{{ t('dialog.group.actions.visibility_friends') }}
</DropdownMenuItem>
<DropdownMenuItem @click="setGroupVisibility(group.id, 'hidden')">
<Check v-if="group.myMember.visibility === 'hidden'" class="size-4" />
</SelectItem>
<SelectItem value="hidden">
{{ t('dialog.group.actions.visibility_hidden') }}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SelectItem>
</SelectContent>
</Select>
<!--//- JSON is missing isSubscribedToAnnouncements, can't be implemented-->
<!-- <el-button
<!-- <Button size="sm" variant="outline"
@click.stop="
setGroupSubscription(group.id, !group.myMember.isSubscribedToAnnouncements)
"
size="small">
">
<span v-if="group.myMember.isSubscribedToAnnouncements"
><el-icon style="margin-left: 5px"><MuteNotification /></el-icon>
{{ t('dialog.group.tags.subscribed') }}</span
@@ -855,24 +842,26 @@
><el-icon style="margin-left: 5px"><Bell /></el-icon>
{{ t('dialog.group.tags.unsubscribed') }}</span
>
</el-button> -->
</Button> -->
<TooltipWrapper side="right" :content="t('dialog.user.groups.leave_group_tooltip')">
<el-button
<Button
class="rounded-full h-6 w-6"
size="icon-sm"
variant="outline"
v-if="shiftHeld"
size="small"
:icon="Close"
circle
style="color: var(--el-color-danger); margin-left: 5px"
@click.stop="leaveGroup(group.id)">
</el-button>
<el-button
<LogOut />
</Button>
<Button
class="rounded-full h-6 w-6 text-red-600"
size="icon-sm"
variant="outline"
v-else
size="small"
:icon="Delete"
circle
style="margin-left: 5px"
@click.stop="leaveGroupPrompt(group.id)">
</el-button>
<LogOut />
</Button>
</TooltipWrapper>
</div>
</div>
@@ -1036,44 +1025,40 @@
}}</span>
</div>
<div style="display: flex; align-items: center">
<span style="margin-right: 5px">{{ t('dialog.user.worlds.sort_by') }}</span>
<DropdownMenu>
<DropdownMenuTrigger as-child :disabled="userDialog.isWorldsLoading">
<el-button size="small" :disabled="userDialog.isWorldsLoading" @click.stop>
<span>
{{ t(userDialog.worldSorting.name) }}
<el-icon style="margin-left: 5px"><ArrowDown /></el-icon>
</span>
</el-button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
<span class="mr-1">{{ t('dialog.user.worlds.sort_by') }}</span>
<Select
:model-value="userDialogWorldSortingKey"
:disabled="userDialog.isWorldsLoading"
@update:modelValue="setUserDialogWorldSortingByKey">
<SelectTrigger size="sm" @click.stop>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem
v-for="(item, key) in userDialogWorldSortingOptions"
:key="key"
@click="setUserDialogWorldSorting(item)">
:key="String(key)"
:value="String(key)">
{{ t(item.name) }}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<span style="margin: 0 5px">{{ t('dialog.user.worlds.order_by') }}</span>
<DropdownMenu>
<DropdownMenuTrigger as-child :disabled="userDialog.isWorldsLoading">
<el-button size="small" :disabled="userDialog.isWorldsLoading" @click.stop>
<span>
{{ t(userDialog.worldOrder.name) }}
<el-icon style="margin-left: 5px"><ArrowDown /></el-icon>
</span>
</el-button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
</SelectItem>
</SelectContent>
</Select>
<span class="ml-2 mr-1">{{ t('dialog.user.worlds.order_by') }}</span>
<Select
:model-value="userDialogWorldOrderKey"
:disabled="userDialog.isWorldsLoading"
@update:modelValue="setUserDialogWorldOrderByKey">
<SelectTrigger size="sm" @click.stop>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem
v-for="(item, key) in userDialogWorldOrderOptions"
:key="key"
@click="setUserDialogWorldOrder(item)">
:key="String(key)"
:value="String(key)">
{{ t(item.name) }}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div
@@ -1097,7 +1082,8 @@
</el-tab-pane>
<el-tab-pane name="Favorite Worlds" :label="t('dialog.user.favorite_worlds.header')" lazy>
<el-button
<!-- <Button
variant="outline"
v-if="userFavoriteWorlds && userFavoriteWorlds.length > 0"
type="default"
:loading="userDialog.isFavoriteWorldsLoading"
@@ -1106,7 +1092,7 @@
circle
style="position: absolute; right: 15px; bottom: 15px; z-index: 99"
@click="getUserFavoriteWorlds(userDialog.id)">
</el-button>
</Button> -->
<el-tabs
ref="favoriteWorldsRef"
v-loading="userDialog.isFavoriteWorldsLoading"
@@ -1186,51 +1172,43 @@
t('dialog.user.avatars.total_count', { count: userDialogAvatars.length })
}}</span>
</div>
<div>
<div class="flex items-center">
<template v-if="userDialog.ref.id === currentUser.id">
<span style="margin-right: 5px">{{ t('dialog.user.avatars.sort_by') }}</span>
<DropdownMenu>
<DropdownMenuTrigger as-child :disabled="userDialog.isWorldsLoading">
<el-button size="small" :disabled="userDialog.isWorldsLoading" @click.stop>
<span>
{{ t(`dialog.user.avatars.sort_by_${userDialog.avatarSorting}`) }}
<el-icon style="margin-left: 5px"><ArrowDown /></el-icon>
</span>
</el-button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem @click="changeUserDialogAvatarSorting('name')">
{{ t('dialog.user.avatars.sort_by_name') }}
</DropdownMenuItem>
<DropdownMenuItem @click="changeUserDialogAvatarSorting('update')">
{{ t('dialog.user.avatars.sort_by_update') }}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<span style="margin-right: 5px; margin-left: 10px">{{
t('dialog.user.avatars.group_by')
}}</span>
<DropdownMenu>
<DropdownMenuTrigger as-child :disabled="userDialog.isWorldsLoading">
<el-button size="small" :disabled="userDialog.isWorldsLoading" @click.stop>
<span>
{{ t(`dialog.user.avatars.${userDialog.avatarReleaseStatus}`) }}
<el-icon style="margin-left: 5px"><ArrowDown /></el-icon>
</span>
</el-button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem @click="userDialog.avatarReleaseStatus = 'all'">
{{ t('dialog.user.avatars.all') }}
</DropdownMenuItem>
<DropdownMenuItem @click="userDialog.avatarReleaseStatus = 'public'">
{{ t('dialog.user.avatars.public') }}
</DropdownMenuItem>
<DropdownMenuItem @click="userDialog.avatarReleaseStatus = 'private'">
{{ t('dialog.user.avatars.private') }}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<span class="mr-1">{{ t('dialog.user.avatars.sort_by') }}</span>
<Select
:model-value="userDialog.avatarSorting"
:disabled="userDialog.isWorldsLoading"
@update:modelValue="changeUserDialogAvatarSorting">
<SelectTrigger size="sm" @click.stop>
<SelectValue
:placeholder="
t(`dialog.user.avatars.sort_by_${userDialog.avatarSorting}`)
" />
</SelectTrigger>
<SelectContent>
<SelectItem value="name">{{
t('dialog.user.avatars.sort_by_name')
}}</SelectItem>
<SelectItem value="update">{{
t('dialog.user.avatars.sort_by_update')
}}</SelectItem>
</SelectContent>
</Select>
<span class="ml-2 mr-1">{{ t('dialog.user.avatars.group_by') }}</span>
<Select
:model-value="userDialog.avatarReleaseStatus"
:disabled="userDialog.isWorldsLoading"
@update:modelValue="(value) => (userDialog.avatarReleaseStatus = value)">
<SelectTrigger size="sm" @click.stop>
<SelectValue
:placeholder="t(`dialog.user.avatars.${userDialog.avatarReleaseStatus}`)" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">{{ t('dialog.user.avatars.all') }}</SelectItem>
<SelectItem value="public">{{ t('dialog.user.avatars.public') }}</SelectItem>
<SelectItem value="private">{{ t('dialog.user.avatars.private') }}</SelectItem>
</SelectContent>
</Select>
</template>
</div>
</div>
@@ -1264,16 +1242,20 @@
</el-tab-pane>
<el-tab-pane name="JSON" :label="t('dialog.user.json.header')" lazy style="height: 50vh">
<el-button type="default" size="small" :icon="Refresh" circle @click="refreshUserDialogTreeData()">
</el-button>
<el-button
type="default"
size="small"
:icon="Download"
circle
style="margin-left: 5px"
<Button
class="rounded-full h-6 w-6 mr-2"
size="icon-sm"
variant="outline"
@click="refreshUserDialogTreeData()">
<RefreshCcw />
</Button>
<Button
class="rounded-full h-6 w-6"
size="icon-sm"
variant="outline"
@click="downloadAndSaveJson(userDialog.id, userDialog.ref)">
</el-button>
<Download />
</Button>
<vue-json-pretty
:data="userDialog.treeData"
:deep="2"
@@ -1304,25 +1286,28 @@
<script setup>
import {
ArrowDown,
Bottom,
Check,
Close,
CollectionTag,
CopyDocument,
Delete,
Download,
Edit,
Download as DownloadIcon,
Loading,
MoreFilled,
Refresh,
Setting,
Top,
View,
Warning
} from '@element-plus/icons-vue';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { computed, defineAsyncComponent, nextTick, ref, watch } from 'vue';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu';
import { Download, LogOut, RefreshCcw } from 'lucide-vue-next';
import { Button } from '@/components/ui/button';
import { ElMessageBox } from 'element-plus';
import { Spinner } from '@/components/ui/spinner';
import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
@@ -1376,13 +1361,6 @@
userRequest,
worldRequest
} from '../../../api';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '../../ui/dropdown-menu';
import { processBulk, request } from '../../../service/request';
import { userDialogGroupSortingOptions, userDialogMutualFriendSortingOptions } from '../../../shared/constants';
import { userDialogWorldOrderOptions, userDialogWorldSortingOptions } from '../../../shared/constants/';
@@ -2368,6 +2346,9 @@
}
async function translateBio() {
if (translateLoading.value) {
return;
}
const bio = userDialog.value.ref.bio;
if (!bio) {
return;
@@ -2455,6 +2436,25 @@
copyToClipboard(displayName, 'User DisplayName copied to clipboard');
}
const userDialogGroupSortingKey = computed(() => {
const current = userDialog.value.groupSorting;
const found = Object.entries(userDialogGroupSortingOptions).find(([, option]) => {
if (option === current) {
return true;
}
return option?.value === current?.value || option?.name === current?.name;
});
return found ? String(found[0]) : '';
});
function setUserDialogGroupSortingByKey(key) {
const option = userDialogGroupSortingOptions[key];
if (!option) {
return;
}
setUserDialogGroupSorting(option);
}
async function setUserDialogGroupSorting(sortOrder) {
const D = userDialog.value;
if (D.groupSorting.value === sortOrder.value) {
@@ -2464,6 +2464,25 @@
await sortCurrentUserGroups();
}
const userDialogMutualFriendSortingKey = computed(() => {
const current = userDialog.value.mutualFriendSorting;
const found = Object.entries(userDialogMutualFriendSortingOptions).find(([, option]) => {
if (option === current) {
return true;
}
return option?.value === current?.value || option?.name === current?.name;
});
return found ? String(found[0]) : '';
});
function setUserDialogMutualFriendSortingByKey(key) {
const option = userDialogMutualFriendSortingOptions[key];
if (!option) {
return;
}
setUserDialogMutualFriendSorting(option);
}
async function setUserDialogMutualFriendSorting(sortOrder) {
const D = userDialog.value;
D.mutualFriendSorting = sortOrder;
@@ -2524,6 +2543,23 @@
});
}
const bulkGroupActionValue = ref('');
function handleBulkGroupAction(value) {
bulkGroupActionValue.value = value;
if (value === 'leave') {
bulkLeaveGroups();
} else if (typeof value === 'string' && value.startsWith('visibility:')) {
const newVisibility = value.slice('visibility:'.length);
bulkSetVisibility(newVisibility);
}
nextTick(() => {
bulkGroupActionValue.value = '';
});
}
// Apply the given visibility to all selected groups
async function bulkSetVisibility(newVisibility) {
for (const groupId of userDialogGroupEditSelectedGroupIds.value) {
@@ -2593,6 +2629,25 @@
refreshUserDialogWorlds();
}
const userDialogWorldSortingKey = computed(() => {
const current = userDialog.value.worldSorting;
const found = Object.entries(userDialogWorldSortingOptions).find(([, option]) => {
if (option === current) {
return true;
}
return option?.value === current?.value || option?.name === current?.name;
});
return found ? String(found[0]) : '';
});
function setUserDialogWorldSortingByKey(key) {
const option = userDialogWorldSortingOptions[key];
if (!option) {
return;
}
setUserDialogWorldSorting(option);
}
async function setUserDialogWorldOrder(order) {
const D = userDialog.value;
if (D.worldOrder.value === order.value) {
@@ -2602,6 +2657,25 @@
refreshUserDialogWorlds();
}
const userDialogWorldOrderKey = computed(() => {
const current = userDialog.value.worldOrder;
const found = Object.entries(userDialogWorldOrderOptions).find(([, option]) => {
if (option === current) {
return true;
}
return option?.value === current?.value || option?.name === current?.name;
});
return found ? String(found[0]) : '';
});
function setUserDialogWorldOrderByKey(key) {
const option = userDialogWorldOrderOptions[key];
if (!option) {
return;
}
setUserDialogWorldOrder(option);
}
function changeUserDialogAvatarSorting(sortOption) {
const D = userDialog.value;
D.avatarSorting = sortOption;

View File

@@ -165,30 +165,37 @@
v-if="worldDialog.inCache"
side="top"
:content="t('dialog.world.actions.delete_cache_tooltip')">
<el-button
:icon="Delete"
size="large"
circle
<Button
class="rounded-full mr-2"
size="icon-lg"
variant="outline"
:disabled="isGameRunning && worldDialog.cacheLocked"
@click="deleteVRChatCache(worldDialog.ref)" />
@click="deleteVRChatCache(worldDialog.ref)"
><Trash2
/></Button>
</TooltipWrapper>
<TooltipWrapper side="top" :content="t('dialog.world.actions.favorites_tooltip')">
<el-button
:type="worldDialog.isFavorite ? 'warning' : 'default'"
:icon="worldDialog.isFavorite ? StarFilled : Star"
size="large"
circle
style="margin-left: 5px"
@click="worldDialogCommand('Add Favorite')" />
<TooltipWrapper
v-if="worldDialog.isFavorite"
side="top"
:content="t('dialog.world.actions.favorites_tooltip')">
<Button class="rounded-full" size="icon-lg" @click="worldDialogCommand('Add Favorite')"
><Star
/></Button>
</TooltipWrapper>
<TooltipWrapper v-else side="top" :content="t('dialog.world.actions.favorites_tooltip')">
<Button
class="rounded-full"
size="icon-lg"
variant="outline"
@click="worldDialogCommand('Add Favorite')"
><Star
/></Button>
</TooltipWrapper>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<el-button
type="default"
:icon="MoreFilled"
size="large"
style="margin-left: 5px"
circle />
<Button variant="outline" size="icon-lg" class="rounded-full ml-2">
<Ellipsis />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem @click="worldDialogCommand('Refresh')">
@@ -343,24 +350,26 @@
<TooltipWrapper
side="top"
:content="t('dialog.world.instances.refresh_instance_info')">
<el-button
size="small"
:icon="Refresh"
style="margin-left: 5px"
circle
@click="refreshInstancePlayerCount(room.tag)" />
<Button
class="rounded-full w-6 h-6 text-xs text-muted-foreground hover:text-foreground"
size="icon"
variant="outline"
@click="refreshInstancePlayerCount(room.tag)"
><i class="ri-refresh-line"></i
></Button>
</TooltipWrapper>
<TooltipWrapper
v-if="instanceJoinHistory.get(room.$location.tag)"
side="top"
:content="t('dialog.previous_instances.info')">
<el-button
size="small"
:icon="DataLine"
<Button
class="rounded-full w-6 h-6 text-xs text-muted-foreground hover:text-foreground"
size="icon-sm"
variant="outline"
style="margin-left: 5px"
plain
circle
@click="showPreviousInstancesInfoDialog(room.location)" />
@click="showPreviousInstancesInfoDialog(room.location)"
><i class="ri-history-line"></i
></Button>
</TooltipWrapper>
<LastJoin :location="room.$location.tag" :currentlocation="lastLocation.location" />
<InstanceInfo
@@ -452,12 +461,13 @@
<TooltipWrapper side="top" :content="t('dialog.world.info.id_tooltip')">
<DropdownMenu>
<DropdownMenuTrigger as-child>
<el-button
type="default"
:icon="CopyDocument"
size="small"
circle
@click.stop />
<Button
class="rounded-full text-xs"
size="icon-sm"
variant="outline"
@click.stop
><i class="ri-file-copy-line"></i
></Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem @click="copyWorldId()">
@@ -690,19 +700,20 @@
</div>
</el-tab-pane>
<el-tab-pane name="JSON" :label="t('dialog.world.json.header')" style="max-height: 50vh" lazy>
<el-button
type="default"
size="small"
:icon="Refresh"
circle
@click="refreshWorldDialogTreeData"></el-button>
<el-button
type="default"
size="small"
:icon="Download"
circle
style="margin-left: 5px"
@click="downloadAndSaveJson(worldDialog.id, worldDialog.ref)"></el-button>
<Button
class="rounded-full h-6 w-6 mr-2"
size="icon-sm"
variant="outline"
@click="refreshWorldDialogTreeData()">
<RefreshCcw />
</Button>
<Button
class="rounded-full h-6 w-6"
size="icon-sm"
variant="outline"
@click="downloadAndSaveJson(worldDialog.id, worldDialog.ref)">
<Download />
</Button>
<vue-json-pretty :data="treeData" :deep="2" :theme="isDarkMode ? 'dark' : 'light'" show-icon />
<br />
<vue-json-pretty
@@ -737,7 +748,6 @@
import {
ArrowDown,
Check,
CopyDocument,
DataLine,
Delete,
Download,
@@ -747,12 +757,9 @@
Loading,
MagicStick,
Message,
MoreFilled,
Picture,
Refresh,
Share,
Star,
StarFilled,
Upload,
User,
UserFilled,
@@ -760,6 +767,8 @@
Warning
} from '@element-plus/icons-vue';
import { computed, defineAsyncComponent, nextTick, ref, watch } from 'vue';
import { Ellipsis, RefreshCcw, Star, Trash2 } from 'lucide-vue-next';
import { Button } from '@/components/ui/button';
import { ElMessageBox } from 'element-plus';
import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner';