feat: add breadcrumb components and main dialog layout functionality

This commit is contained in:
pa
2026-01-20 17:30:23 +09:00
committed by Natsumi
parent 0b636df330
commit b2bd7693bb
21 changed files with 3768 additions and 3471 deletions
@@ -1,9 +1,4 @@
<template> <template>
<Dialog v-model:open="avatarDialog.visible">
<DialogContent
class="x-dialog x-avatar-dialog sm:max-w-235 translate-y-0"
style="top: 10vh"
:show-close-button="false">
<div> <div>
<div style="display: flex"> <div style="display: flex">
<img <img
@@ -82,22 +77,12 @@
><Apple class="h-4 w-4 text-[#8e8e93]" /> ><Apple class="h-4 w-4 text-[#8e8e93]" />
<span <span
v-if="avatarDialog.platformInfo.ios" v-if="avatarDialog.platformInfo.ios"
:class="[ :class="['x-grey', 'x-tag-border-left', 'text-[#8e8e93]', 'border-[#8e8e93]']"
'x-grey',
'x-tag-border-left',
'text-[#8e8e93]',
'border-[#8e8e93]'
]"
>{{ avatarDialog.platformInfo.ios.performanceRating }}</span >{{ avatarDialog.platformInfo.ios.performanceRating }}</span
> >
<span <span
v-if="avatarDialog.bundleSizes['ios']" v-if="avatarDialog.bundleSizes['ios']"
:class="[ :class="['x-grey', 'x-tag-border-left', 'text-[#8e8e93]', 'border-[#8e8e93]']"
'x-grey',
'x-tag-border-left',
'text-[#8e8e93]',
'border-[#8e8e93]'
]"
>{{ avatarDialog.bundleSizes['ios'].fileSize }}</span >{{ avatarDialog.bundleSizes['ios'].fileSize }}</span
> >
</Badge> </Badge>
@@ -116,16 +101,12 @@
variant="outline" variant="outline"
style="margin-right: 5px; margin-top: 5px" style="margin-right: 5px; margin-top: 5px"
>Styles >Styles
<span <span v-if="avatarDialog.ref.styles.primary" :class="['x-grey', 'x-tag-border-left']">{{
v-if="avatarDialog.ref.styles.primary" avatarDialog.ref.styles.primary
:class="['x-grey', 'x-tag-border-left']" }}</span>
>{{ avatarDialog.ref.styles.primary }}</span <span v-if="avatarDialog.ref.styles.secondary" :class="['x-grey', 'x-tag-border-left']">{{
> avatarDialog.ref.styles.secondary
<span }}</span>
v-if="avatarDialog.ref.styles.secondary"
:class="['x-grey', 'x-tag-border-left']"
>{{ avatarDialog.ref.styles.secondary }}</span
>
</Badge> </Badge>
<Badge <Badge
v-if="avatarDialog.isQuestFallback" v-if="avatarDialog.isQuestFallback"
@@ -339,7 +320,7 @@
</DropdownMenu> </DropdownMenu>
</div> </div>
</div> </div>
</div>
<TabsUnderline <TabsUnderline
v-model="avatarDialogActiveTab" v-model="avatarDialogActiveTab"
:items="avatarDialogTabs" :items="avatarDialogTabs"
@@ -370,9 +351,7 @@
<div class="mt-2 w-[80%] ml-20"> <div class="mt-2 w-[80%] ml-20">
<Carousel v-if="avatarDialog.galleryImages.length" class="w-full"> <Carousel v-if="avatarDialog.galleryImages.length" class="w-full">
<CarouselContent class="h-50"> <CarouselContent class="h-50">
<CarouselItem <CarouselItem v-for="imageUrl in avatarDialog.galleryImages" :key="imageUrl">
v-for="imageUrl in avatarDialog.galleryImages"
:key="imageUrl">
<div class="relative h-50 w-full"> <div class="relative h-50 w-full">
<img <img
:src="imageUrl" :src="imageUrl"
@@ -414,9 +393,7 @@
</div> </div>
<div class="x-friend-item" style="width: 100%; cursor: default"> <div class="x-friend-item" style="width: 100%; cursor: default">
<div class="detail"> <div class="detail">
<span class="name" style="margin-bottom: 5px">{{ <span class="name" style="margin-bottom: 5px">{{ t('dialog.avatar.info.memo') }}</span>
t('dialog.avatar.info.memo')
}}</span>
<InputGroupTextareaField <InputGroupTextareaField
v-model="memo" v-model="memo"
class="extra" class="extra"
@@ -458,9 +435,7 @@
<div class="x-friend-item" style="cursor: default"> <div class="x-friend-item" style="cursor: default">
<div class="detail"> <div class="detail">
<span class="name">{{ t('dialog.avatar.info.created_at') }}</span> <span class="name">{{ t('dialog.avatar.info.created_at') }}</span>
<span class="extra">{{ <span class="extra">{{ formatDateFilter(avatarDialog.ref.created_at, 'long') }}</span>
formatDateFilter(avatarDialog.ref.created_at, 'long')
}}</span>
</div> </div>
</div> </div>
<div class="x-friend-item" style="cursor: default"> <div class="x-friend-item" style="cursor: default">
@@ -495,10 +470,7 @@
<div class="x-friend-item" style="width: 100%; cursor: default"> <div class="x-friend-item" style="width: 100%; cursor: default">
<div class="detail"> <div class="detail">
<span class="name">{{ t('dialog.avatar.info.platform') }}</span> <span class="name">{{ t('dialog.avatar.info.platform') }}</span>
<span <span v-if="avatarDialogPlatform" class="extra" v-text="avatarDialogPlatform"></span>
v-if="avatarDialogPlatform"
class="extra"
v-text="avatarDialogPlatform"></span>
<span v-else class="extra">-</span> <span v-else class="extra">-</span>
</div> </div>
</div> </div>
@@ -537,8 +509,7 @@
v-model:previousImageUrl="previousImageUrl" /> v-model:previousImageUrl="previousImageUrl" />
</template> </template>
</div> </div>
</DialogContent> </div>
</Dialog>
</template> </template>
<script setup> <script setup>
@@ -563,7 +534,6 @@
} from 'lucide-vue-next'; } from 'lucide-vue-next';
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/components/ui/carousel'; import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/components/ui/carousel';
import { computed, defineAsyncComponent, nextTick, ref, watch } from 'vue'; import { computed, defineAsyncComponent, nextTick, ref, watch } from 'vue';
import { Dialog, DialogContent } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';
import { TabsUnderline } from '@/components/ui/tabs'; import { TabsUnderline } from '@/components/ui/tabs';
@@ -1,9 +1,5 @@
<template> <template>
<Dialog v-model:open="groupDialog.visible"> <div>
<DialogContent
class="x-dialog x-group-dialog group-body translate-y-0 sm:max-w-235"
:show-close-button="false"
style="top: 10vh">
<DialogHeader class="sr-only"> <DialogHeader class="sr-only">
<DialogTitle>{{ groupDialog.ref?.name || t('dialog.group.info.header') }}</DialogTitle> <DialogTitle>{{ groupDialog.ref?.name || t('dialog.group.info.header') }}</DialogTitle>
<DialogDescription> <DialogDescription>
@@ -157,10 +153,7 @@
<Star /> <Star />
</Button> </Button>
</TooltipWrapper> </TooltipWrapper>
<TooltipWrapper <TooltipWrapper v-else side="top" :content="t('dialog.group.actions.represent_tooltip')">
v-else
side="top"
:content="t('dialog.group.actions.represent_tooltip')">
<span> <span>
<Button <Button
class="rounded-full mr-2" class="rounded-full mr-2"
@@ -174,9 +167,7 @@
</TooltipWrapper> </TooltipWrapper>
</template> </template>
<template v-else-if="groupDialog.ref.myMember?.membershipStatus === 'requested'"> <template v-else-if="groupDialog.ref.myMember?.membershipStatus === 'requested'">
<TooltipWrapper <TooltipWrapper side="top" :content="t('dialog.group.actions.cancel_join_request_tooltip')">
side="top"
:content="t('dialog.group.actions.cancel_join_request_tooltip')">
<span> <span>
<Button <Button
class="rounded-full mr-2" class="rounded-full mr-2"
@@ -242,9 +233,7 @@
<Button <Button
class="rounded-full" class="rounded-full"
:variant=" :variant="
groupDialog.ref.membershipStatus === 'userblocked' groupDialog.ref.membershipStatus === 'userblocked' ? 'destructive' : 'outline'
? 'destructive'
: 'outline'
" "
size="icon-lg"> size="icon-lg">
<MoreHorizontal /> <MoreHorizontal />
@@ -294,9 +283,7 @@
{{ t('dialog.group.actions.moderation_tools') }} {{ t('dialog.group.actions.moderation_tools') }}
</DropdownMenuItem> </DropdownMenuItem>
<template <template
v-if=" v-if="groupDialog.ref.myMember && groupDialog.ref.privacy === 'default'">
groupDialog.ref.myMember && groupDialog.ref.privacy === 'default'
">
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem @click="groupDialogCommand('Visibility Everyone')"> <DropdownMenuItem @click="groupDialogCommand('Visibility Everyone')">
<Eye class="size-4" /> <Eye class="size-4" />
@@ -358,13 +345,7 @@
<img <img
:src="groupDialog.ref.bannerUrl" :src="groupDialog.ref.bannerUrl"
class="cursor-pointer" class="cursor-pointer"
style=" style="flex: none; width: 100%; aspect-ratio: 6/1; object-fit: cover; border-radius: 4px"
flex: none;
width: 100%;
aspect-ratio: 6/1;
object-fit: cover;
border-radius: 4px;
"
@click="showFullscreenImageDialog(groupDialog.ref.bannerUrl)" @click="showFullscreenImageDialog(groupDialog.ref.bannerUrl)"
loading="lazy" /> loading="lazy" />
</div> </div>
@@ -456,12 +437,8 @@
<template #content> <template #content>
<span>{{ t('dialog.group.posts.visibility') }}</span> <span>{{ t('dialog.group.posts.visibility') }}</span>
<br /> <br />
<template <template v-for="roleId in groupDialog.announcement.roleIds" :key="roleId">
v-for="roleId in groupDialog.announcement.roleIds" <template v-for="role in groupDialog.ref.roles" :key="roleId + role.id"
:key="roleId">
<template
v-for="role in groupDialog.ref.roles"
:key="roleId + role.id"
><span v-if="role.id === roleId" v-text="role.name" ><span v-if="role.id === roleId" v-text="role.name"
/></template> /></template>
<span <span
@@ -486,9 +463,7 @@
<template #content> <template #content>
<span <span
>{{ t('dialog.group.posts.created_at') }} >{{ t('dialog.group.posts.created_at') }}
{{ {{ formatDateFilter(groupDialog.announcement.createdAt, 'long') }}</span
formatDateFilter(groupDialog.announcement.createdAt, 'long')
}}</span
> >
<template <template
v-if=" v-if="
@@ -506,23 +481,17 @@
</template> </template>
<Timer :epoch="Date.parse(groupDialog.announcement.updatedAt)" /> <Timer :epoch="Date.parse(groupDialog.announcement.updatedAt)" />
</TooltipWrapper> </TooltipWrapper>
<template <template v-if="hasGroupPermission(groupDialog.ref, 'group-announcement-manage')">
v-if="hasGroupPermission(groupDialog.ref, 'group-announcement-manage')">
<TooltipWrapper side="top" :content="t('dialog.group.posts.edit_tooltip')"> <TooltipWrapper side="top" :content="t('dialog.group.posts.edit_tooltip')">
<Button <Button
size="sm" size="sm"
variant="ghost" variant="ghost"
style="margin-left: 5px; padding: 0" style="margin-left: 5px; padding: 0"
@click=" @click="
showGroupPostEditDialog( showGroupPostEditDialog(groupDialog.id, groupDialog.announcement)
groupDialog.id,
groupDialog.announcement
)
"></Button> "></Button>
</TooltipWrapper> </TooltipWrapper>
<TooltipWrapper <TooltipWrapper side="top" :content="t('dialog.group.posts.delete_tooltip')">
side="top"
:content="t('dialog.group.posts.delete_tooltip')">
<Button <Button
size="sm" size="sm"
variant="ghost" variant="ghost"
@@ -690,14 +659,10 @@
<span class="name">{{ t('dialog.group.info.roles') }}</span> <span class="name">{{ t('dialog.group.info.roles') }}</span>
<span v-if="groupDialog.memberRoles.length === 0" class="extra"> - </span> <span v-if="groupDialog.memberRoles.length === 0" class="extra"> - </span>
<span v-else class="extra"> <span v-else class="extra">
<template <template v-for="(role, rIndex) in groupDialog.memberRoles" :key="rIndex">
v-for="(role, rIndex) in groupDialog.memberRoles"
:key="rIndex">
<TooltipWrapper side="top"> <TooltipWrapper side="top">
<template #content> <template #content>
<span <span>{{ t('dialog.group.info.role') }} {{ role.name }}</span>
>{{ t('dialog.group.info.role') }} {{ role.name }}</span
>
<br /> <br />
<span <span
>{{ t('dialog.group.info.role_description') }} >{{ t('dialog.group.info.role_description') }}
@@ -796,9 +761,7 @@
><span v-if="role.id === roleId" v-text="role.name" /> ><span v-if="role.id === roleId" v-text="role.name" />
</template> </template>
<template <template
v-if=" v-if="post.roleIds.indexOf(roleId) < post.roleIds.length - 1"
post.roleIds.indexOf(roleId) < post.roleIds.length - 1
"
><span>,&nbsp;</span></template ><span>,&nbsp;</span></template
> >
</template> </template>
@@ -828,9 +791,7 @@
</TooltipWrapper> </TooltipWrapper>
<template <template
v-if="hasGroupPermission(groupDialog.ref, 'group-announcement-manage')"> v-if="hasGroupPermission(groupDialog.ref, 'group-announcement-manage')">
<TooltipWrapper <TooltipWrapper side="top" :content="t('dialog.group.posts.edit_tooltip')">
side="top"
:content="t('dialog.group.posts.edit_tooltip')">
<Button <Button
size="icon-sm" size="icon-sm"
class="h-6 w-6 text-xs text-muted-foreground hover:text-foreground" class="h-6 w-6 text-xs text-muted-foreground hover:text-foreground"
@@ -888,9 +849,7 @@
<span <span
v-if="groupDialog.memberSearch.length" v-if="groupDialog.memberSearch.length"
style="font-size: 14px; margin-left: 5px; margin-right: 5px" style="font-size: 14px; margin-left: 5px; margin-right: 5px"
>{{ groupDialog.memberSearchResults.length }}/{{ >{{ groupDialog.memberSearchResults.length }}/{{ groupDialog.ref.memberCount }}</span
groupDialog.ref.memberCount
}}</span
> >
<span v-else style="font-size: 14px; margin-left: 5px; margin-right: 5px" <span v-else style="font-size: 14px; margin-left: 5px; margin-right: 5px"
>{{ groupDialog.members.length }}/{{ groupDialog.ref.memberCount }}</span >{{ groupDialog.members.length }}/{{ groupDialog.ref.memberCount }}</span
@@ -960,8 +919,7 @@
:style="{ color: user.user?.$userColour }" :style="{ color: user.user?.$userColour }"
v-text="user.user?.displayName" /> v-text="user.user?.displayName" />
<span class="extra"> <span class="extra">
<template <template v-if="hasGroupPermission(groupDialog.ref, 'group-members-manage')">
v-if="hasGroupPermission(groupDialog.ref, 'group-members-manage')">
<TooltipWrapper <TooltipWrapper
v-if="user.isRepresenting" v-if="user.isRepresenting"
side="top" side="top"
@@ -1021,8 +979,7 @@
:style="{ color: user.user?.$userColour }" :style="{ color: user.user?.$userColour }"
v-text="user.user?.displayName" /> v-text="user.user?.displayName" />
<span class="extra"> <span class="extra">
<template <template v-if="hasGroupPermission(groupDialog.ref, 'group-members-manage')">
v-if="hasGroupPermission(groupDialog.ref, 'group-members-manage')">
<TooltipWrapper <TooltipWrapper
v-if="user.isRepresenting" v-if="user.isRepresenting"
side="top" side="top"
@@ -1095,10 +1052,7 @@
:key="`label-${index}`" :key="`label-${index}`"
v-slot:[`label-${index}`]> v-slot:[`label-${index}`]>
<span style="font-weight: bold; font-size: 16px" v-text="gallery.name" /> <span style="font-weight: bold; font-size: 16px" v-text="gallery.name" />
<i <i class="x-status-icon" style="margin-left: 5px" :class="groupGalleryStatus(gallery)" />
class="x-status-icon"
style="margin-left: 5px"
:class="groupGalleryStatus(gallery)" />
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{ <span style="color: #909399; font-size: 12px; margin-left: 5px">{{
groupDialog.galleries[gallery.id] ? groupDialog.galleries[gallery.id].length : 0 groupDialog.galleries[gallery.id] ? groupDialog.galleries[gallery.id].length : 0
}}</span> }}</span>
@@ -1154,10 +1108,9 @@
</template> </template>
</TabsUnderline> </TabsUnderline>
</div> </div>
</DialogContent>
<GroupPostEditDialog :dialog-data="groupPostEditDialog" :selected-gallery-file="selectedGalleryFile" /> <GroupPostEditDialog :dialog-data="groupPostEditDialog" :selected-gallery-file="selectedGalleryFile" />
<PreviousInstancesGroupDialog v-model:previous-instances-group-dialog="previousInstancesGroupDialog" /> <PreviousInstancesGroupDialog v-model:previous-instances-group-dialog="previousInstancesGroupDialog" />
</Dialog> </div>
</template> </template>
<script setup> <script setup>
@@ -1182,9 +1135,9 @@
X, X,
XCircle XCircle
} from 'lucide-vue-next'; } from 'lucide-vue-next';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { computed, nextTick, reactive, ref, watch } from 'vue'; import { computed, nextTick, reactive, ref, watch } from 'vue';
import { DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card'; import { Card } from '@/components/ui/card';
import { InputGroupField } from '@/components/ui/input-group'; import { InputGroupField } from '@/components/ui/input-group';
@@ -0,0 +1,210 @@
<script setup>
import {
Breadcrumb,
BreadcrumbEllipsis,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator
} from '@/components/ui/breadcrumb';
import { useAvatarStore, useGroupStore, useUiStore, useUserStore, useWorldStore } from '@/stores';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu';
import { Dialog, DialogContent } from '@/components/ui/dialog';
import { computed } from 'vue';
import AvatarDialog from './AvatarDialog/AvatarDialog.vue';
import GroupDialog from './GroupDialog/GroupDialog.vue';
import UserDialog from './UserDialog/UserDialog.vue';
import WorldDialog from './WorldDialog/WorldDialog.vue';
const avatarStore = useAvatarStore();
const groupStore = useGroupStore();
const uiStore = useUiStore();
const userStore = useUserStore();
const worldStore = useWorldStore();
const isOpen = computed({
get: () =>
userStore.userDialog.visible ||
worldStore.worldDialog.visible ||
avatarStore.avatarDialog.visible ||
groupStore.groupDialog.visible,
set: (value) => {
if (!value) {
userStore.userDialog.visible = false;
worldStore.worldDialog.visible = false;
avatarStore.avatarDialog.visible = false;
groupStore.groupDialog.visible = false;
uiStore.clearDialogCrumbs();
}
}
});
const dialogCrumbs = computed(() => uiStore.dialogCrumbs);
const activeCrumb = computed(() => dialogCrumbs.value[dialogCrumbs.value.length - 1] || null);
const activeType = computed(() => {
if (activeCrumb.value?.type) {
return activeCrumb.value.type;
}
if (userStore.userDialog.visible) {
return 'user';
}
if (worldStore.worldDialog.visible) {
return 'world';
}
if (avatarStore.avatarDialog.visible) {
return 'avatar';
}
if (groupStore.groupDialog.visible) {
return 'group';
}
return null;
});
const activeComponent = computed(() => {
switch (activeType.value) {
case 'user':
return UserDialog;
case 'world':
return WorldDialog;
case 'avatar':
return AvatarDialog;
case 'group':
return GroupDialog;
default:
return null;
}
});
const dialogClass = computed(() => {
switch (activeType.value) {
case 'world':
return 'x-dialog x-world-dialog translate-y-0 sm:max-w-235';
case 'avatar':
return 'x-dialog x-avatar-dialog sm:max-w-235 translate-y-0';
case 'group':
return 'x-dialog x-group-dialog group-body translate-y-0 sm:max-w-235';
case 'user':
default:
return 'x-dialog x-user-dialog sm:max-w-235 translate-y-0';
}
});
const shouldShowBreadcrumbs = computed(() => dialogCrumbs.value.length > 1);
const shouldCollapseBreadcrumbs = computed(() => dialogCrumbs.value.length > 4);
const middleBreadcrumbs = computed(() => {
if (!shouldCollapseBreadcrumbs.value) {
return [];
}
return dialogCrumbs.value.slice(1, -2);
});
const handleBreadcrumbClick = (index) => {
const item = dialogCrumbs.value[index];
if (!item) {
return;
}
uiStore.jumpDialogCrumb(index);
if (item.type === 'user') {
userStore.showUserDialog(item.id, { skipBreadcrumb: true });
return;
}
if (item.type === 'world') {
worldStore.showWorldDialog(item.id, null, { skipBreadcrumb: true });
return;
}
if (item.type === 'avatar') {
avatarStore.showAvatarDialog(item.id, { skipBreadcrumb: true });
return;
}
if (item.type === 'group') {
groupStore.showGroupDialog(item.id, { skipBreadcrumb: true });
}
};
</script>
<template>
<Dialog v-model:open="isOpen">
<DialogContent :class="dialogClass" style="top: 10vh" :show-close-button="false">
<Breadcrumb v-if="shouldShowBreadcrumbs" class="mb-2">
<BreadcrumbList>
<template v-if="shouldCollapseBreadcrumbs">
<BreadcrumbItem>
<BreadcrumbLink as-child>
<button
type="button"
class="max-w-40 truncate text-left"
@click="handleBreadcrumbClick(0)">
{{ dialogCrumbs[0]?.label || dialogCrumbs[0]?.id }}
</button>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<DropdownMenu>
<DropdownMenuTrigger class="flex items-center gap-1">
<BreadcrumbEllipsis class="h-4 w-4" />
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem
v-for="(crumb, index) in middleBreadcrumbs"
:key="`${crumb.type}-${crumb.id}`"
@click="handleBreadcrumbClick(index + 1)">
{{ crumb.label || crumb.id }}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink as-child>
<button
type="button"
class="max-w-40 truncate text-left"
@click="handleBreadcrumbClick(dialogCrumbs.length - 2)">
{{
dialogCrumbs[dialogCrumbs.length - 2]?.label ||
dialogCrumbs[dialogCrumbs.length - 2]?.id
}}
</button>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage class="max-w-40 truncate">
{{
dialogCrumbs[dialogCrumbs.length - 1]?.label ||
dialogCrumbs[dialogCrumbs.length - 1]?.id
}}
</BreadcrumbPage>
</BreadcrumbItem>
</template>
<template v-else>
<template v-for="(crumb, index) in dialogCrumbs" :key="`${crumb.type}-${crumb.id}`">
<BreadcrumbItem>
<BreadcrumbLink v-if="index < dialogCrumbs.length - 1" as-child>
<button
type="button"
class="max-w-40 truncate text-left"
@click="handleBreadcrumbClick(index)">
{{ crumb.label || crumb.id }}
</button>
</BreadcrumbLink>
<BreadcrumbPage v-else class="max-w-40 truncate">
{{ crumb.label || crumb.id }}
</BreadcrumbPage>
</BreadcrumbItem>
<BreadcrumbSeparator v-if="index < dialogCrumbs.length - 1" />
</template>
</template>
</BreadcrumbList>
</Breadcrumb>
<component :is="activeComponent" v-if="activeComponent" />
</DialogContent>
</Dialog>
</template>
+37 -126
View File
@@ -1,9 +1,4 @@
<template> <template>
<Dialog v-model:open="userDialog.visible">
<DialogContent
class="x-dialog x-user-dialog sm:max-w-235 translate-y-0"
style="top: 10vh"
:show-close-button="false">
<div> <div>
<DialogHeader class="sr-only"> <DialogHeader class="sr-only">
<DialogTitle>{{ <DialogTitle>{{
@@ -148,10 +143,7 @@
: t('dialog.user.info.avatar_info') : t('dialog.user.info.avatar_info')
}} }}
<TooltipWrapper <TooltipWrapper
v-if=" v-if="userDialog.ref.profilePicOverride && !userDialog.ref.currentAvatarImageUrl"
userDialog.ref.profilePicOverride &&
!userDialog.ref.currentAvatarImageUrl
"
side="top" side="top"
:content="t('dialog.user.info.vrcplus_hides_avatar')"> :content="t('dialog.user.info.vrcplus_hides_avatar')">
<Info /> <Info />
@@ -200,9 +192,7 @@
style="margin-right: 5px" style="margin-right: 5px"
>👑</span >👑</span
> >
<span <span style="margin-right: 5px" v-text="userDialog.representedGroup.name"></span>
style="margin-right: 5px"
v-text="userDialog.representedGroup.name"></span>
<span>({{ userDialog.representedGroup.memberCount }})</span> <span>({{ userDialog.representedGroup.memberCount }})</span>
</span> </span>
</div> </div>
@@ -284,9 +274,7 @@
{{ t('dialog.user.info.join_count') }} {{ t('dialog.user.info.join_count') }}
</div> </div>
<TooltipWrapper <TooltipWrapper side="top" :content="t('dialog.user.info.open_previous_instance')">
side="top"
:content="t('dialog.user.info.open_previous_instance')">
<MoreHorizontal style="margin-right: 16px" /> <MoreHorizontal style="margin-right: 16px" />
</TooltipWrapper> </TooltipWrapper>
</div> </div>
@@ -382,9 +370,7 @@
<span v-else class="name"> <span v-else class="name">
{{ t('dialog.user.info.friended') }} {{ t('dialog.user.info.friended') }}
</span> </span>
<span class="extra">{{ <span class="extra">{{ formatDateFilter(userDialog.dateFriended, 'long') }}</span>
formatDateFilter(userDialog.dateFriended, 'long')
}}</span>
</div> </div>
</TooltipWrapper> </TooltipWrapper>
</div> </div>
@@ -395,9 +381,7 @@
<span v-if="currentUser.allowAvatarCopying" class="extra">{{ <span v-if="currentUser.allowAvatarCopying" class="extra">{{
t('dialog.user.info.avatar_cloning_allow') t('dialog.user.info.avatar_cloning_allow')
}}</span> }}</span>
<span v-else class="extra">{{ <span v-else class="extra">{{ t('dialog.user.info.avatar_cloning_deny') }}</span>
t('dialog.user.info.avatar_cloning_deny')
}}</span>
</div> </div>
</div> </div>
<div class="x-friend-item" @click="toggleAllowBooping"> <div class="x-friend-item" @click="toggleAllowBooping">
@@ -406,9 +390,7 @@
<span v-if="currentUser.isBoopingEnabled" class="extra">{{ <span v-if="currentUser.isBoopingEnabled" class="extra">{{
t('dialog.user.info.avatar_cloning_allow') t('dialog.user.info.avatar_cloning_allow')
}}</span> }}</span>
<span v-else class="extra">{{ <span v-else class="extra">{{ t('dialog.user.info.avatar_cloning_deny') }}</span>
t('dialog.user.info.avatar_cloning_deny')
}}</span>
</div> </div>
</div> </div>
<div class="x-friend-item" @click="toggleSharedConnectionsOptOut"> <div class="x-friend-item" @click="toggleSharedConnectionsOptOut">
@@ -417,9 +399,7 @@
<span v-if="!currentUser.hasSharedConnectionsOptOut" class="extra">{{ <span v-if="!currentUser.hasSharedConnectionsOptOut" class="extra">{{
t('dialog.user.info.avatar_cloning_allow') t('dialog.user.info.avatar_cloning_allow')
}}</span> }}</span>
<span v-else class="extra">{{ <span v-else class="extra">{{ t('dialog.user.info.avatar_cloning_deny') }}</span>
t('dialog.user.info.avatar_cloning_deny')
}}</span>
</div> </div>
</div> </div>
</template> </template>
@@ -430,16 +410,11 @@
<span v-if="userDialog.ref.allowAvatarCopying" class="extra">{{ <span v-if="userDialog.ref.allowAvatarCopying" class="extra">{{
t('dialog.user.info.avatar_cloning_allow') t('dialog.user.info.avatar_cloning_allow')
}}</span> }}</span>
<span v-else class="extra">{{ <span v-else class="extra">{{ t('dialog.user.info.avatar_cloning_deny') }}</span>
t('dialog.user.info.avatar_cloning_deny')
}}</span>
</div> </div>
</div> </div>
</template> </template>
<div <div v-if="userDialog.ref.id === currentUser.id" class="x-friend-item" @click="getVRChatCredits()">
v-if="userDialog.ref.id === currentUser.id"
class="x-friend-item"
@click="getVRChatCredits()">
<div class="detail"> <div class="detail">
<span class="name">{{ t('view.profile.profile.vrchat_credits') }}</span> <span class="name">{{ t('view.profile.profile.vrchat_credits') }}</span>
<span class="extra">{{ vrchatCredit ?? t('view.profile.profile.refresh') }}</span> <span class="extra">{{ vrchatCredit ?? t('view.profile.profile.refresh') }}</span>
@@ -487,8 +462,7 @@
<DropdownMenuItem @click="copyUserURL(userDialog.id)"> <DropdownMenuItem @click="copyUserURL(userDialog.id)">
{{ t('dialog.user.info.copy_url') }} {{ t('dialog.user.info.copy_url') }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem @click="copyUserDisplayName(userDialog.ref.displayName)">
@click="copyUserDisplayName(userDialog.ref.displayName)">
{{ t('dialog.user.info.copy_display_name') }} {{ t('dialog.user.info.copy_display_name') }}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
@@ -500,9 +474,7 @@
</div> </div>
</template> </template>
<template <template v-if="userDialog.id !== currentUser.id && !currentUser.hasSharedConnectionsOptOut" #mutual>
v-if="userDialog.id !== currentUser.id && !currentUser.hasSharedConnectionsOptOut"
#mutual>
<div style="display: flex; align-items: center; justify-content: space-between"> <div style="display: flex; align-items: center; justify-content: space-between">
<div style="display: flex; align-items: center"> <div style="display: flex; align-items: center">
<Button <Button
@@ -552,9 +524,7 @@
<AlertTriangle style="margin-right: 5px" /> <AlertTriangle style="margin-right: 5px" />
<span>Mutual Friends unavailable due to VRChat staged rollout, click for more info</span> <span>Mutual Friends unavailable due to VRChat staged rollout, click for more info</span>
</div> </div>
<ul <ul class="x-friend-list" style="margin-top: 10px; overflow: auto; max-height: 250px; min-width: 130px">
class="x-friend-list"
style="margin-top: 10px; overflow: auto; max-height: 250px; min-width: 130px">
<li <li
v-for="user in userDialog.mutualFriends" v-for="user in userDialog.mutualFriends"
:key="user.id" :key="user.id"
@@ -564,10 +534,7 @@
<img :src="userImage(user)" loading="lazy" /> <img :src="userImage(user)" loading="lazy" />
</div> </div>
<div class="detail"> <div class="detail">
<span <span class="name" :style="{ color: user.$userColour }" v-text="user.displayName"></span>
class="name"
:style="{ color: user.$userColour }"
v-text="user.displayName"></span>
</div> </div>
</li> </li>
</ul> </ul>
@@ -642,17 +609,10 @@
</div> </div>
<div style="margin-top: 10px"> <div style="margin-top: 10px">
<template v-if="userDialogGroupEditMode"> <template v-if="userDialogGroupEditMode">
<div <div class="x-friend-list" style="margin-top: 10px; margin-bottom: 15px; max-height: unset">
class="x-friend-list"
style="margin-top: 10px; margin-bottom: 15px; max-height: unset">
<!-- Bulk actions dropdown (shown only in edit mode) --> <!-- Bulk actions dropdown (shown only in edit mode) -->
<Select <Select :model-value="bulkGroupActionValue" @update:modelValue="handleBulkGroupAction">
:model-value="bulkGroupActionValue" <SelectTrigger size="sm" style="margin-right: 5px; margin-bottom: 5px" @click.stop>
@update:modelValue="handleBulkGroupAction">
<SelectTrigger
size="sm"
style="margin-right: 5px; margin-bottom: 5px"
@click.stop>
<SelectValue :placeholder="t('dialog.group.actions.manage_selected')" /> <SelectValue :placeholder="t('dialog.group.actions.manage_selected')" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -754,9 +714,7 @@
:content="t('dialog.group.members.representing')"> :content="t('dialog.group.members.representing')">
<Tag style="margin-right: 5px" /> <Tag style="margin-right: 5px" />
</TooltipWrapper> </TooltipWrapper>
<TooltipWrapper <TooltipWrapper v-if="group.myMember?.visibility !== 'visible'" side="top">
v-if="group.myMember?.visibility !== 'visible'"
side="top">
<template #content> <template #content>
<span <span
>{{ t('dialog.group.members.visibility') }} >{{ t('dialog.group.members.visibility') }}
@@ -811,9 +769,7 @@
{{ t('dialog.group.tags.unsubscribed') }}</span {{ t('dialog.group.tags.unsubscribed') }}</span
> >
</Button> --> </Button> -->
<TooltipWrapper <TooltipWrapper side="right" :content="t('dialog.user.groups.leave_group_tooltip')">
side="right"
:content="t('dialog.user.groups.leave_group_tooltip')">
<Button <Button
class="rounded-full h-6 w-6" class="rounded-full h-6 w-6"
size="icon-sm" size="icon-sm"
@@ -846,9 +802,7 @@
cachedConfig?.constants?.GROUPS?.MAX_OWNED cachedConfig?.constants?.GROUPS?.MAX_OWNED
}}</span }}</span
> >
<div <div class="x-friend-list" style="margin-top: 10px; margin-bottom: 15px; min-height: 60px">
class="x-friend-list"
style="margin-top: 10px; margin-bottom: 15px; min-height: 60px">
<div <div
v-for="group in userGroups.ownGroups" v-for="group in userGroups.ownGroups"
:key="group.id" :key="group.id"
@@ -866,9 +820,7 @@
:content="t('dialog.group.members.representing')"> :content="t('dialog.group.members.representing')">
<Tag style="margin-right: 5px" /> <Tag style="margin-right: 5px" />
</TooltipWrapper> </TooltipWrapper>
<TooltipWrapper <TooltipWrapper v-if="group.memberVisibility !== 'visible'" side="top">
v-if="group.memberVisibility !== 'visible'"
side="top">
<template #content> <template #content>
<span <span
>{{ t('dialog.group.members.visibility') }} >{{ t('dialog.group.members.visibility') }}
@@ -887,12 +839,8 @@
<span style="font-weight: bold; font-size: 16px">{{ <span style="font-weight: bold; font-size: 16px">{{
t('dialog.user.groups.mutual_groups') t('dialog.user.groups.mutual_groups')
}}</span> }}</span>
<span style="font-size: 12px; margin-left: 5px">{{ <span style="font-size: 12px; margin-left: 5px">{{ userGroups.mutualGroups.length }}</span>
userGroups.mutualGroups.length <div class="x-friend-list" style="margin-top: 10px; margin-bottom: 15px; min-height: 60px">
}}</span>
<div
class="x-friend-list"
style="margin-top: 10px; margin-bottom: 15px; min-height: 60px">
<div <div
v-for="group in userGroups.mutualGroups" v-for="group in userGroups.mutualGroups"
:key="group.id" :key="group.id"
@@ -910,9 +858,7 @@
:content="t('dialog.group.members.representing')"> :content="t('dialog.group.members.representing')">
<Tag style="margin-right: 5px" /> <Tag style="margin-right: 5px" />
</TooltipWrapper> </TooltipWrapper>
<TooltipWrapper <TooltipWrapper v-if="group.memberVisibility !== 'visible'" side="top">
v-if="group.memberVisibility !== 'visible'"
side="top">
<template #content> <template #content>
<span <span
>{{ t('dialog.group.members.visibility') }} >{{ t('dialog.group.members.visibility') }}
@@ -928,9 +874,7 @@
</div> </div>
</template> </template>
<template v-if="userGroups.remainingGroups.length > 0"> <template v-if="userGroups.remainingGroups.length > 0">
<span style="font-weight: bold; font-size: 16px">{{ <span style="font-weight: bold; font-size: 16px">{{ t('dialog.user.groups.groups') }}</span>
t('dialog.user.groups.groups')
}}</span>
<span style="font-size: 12px; margin-left: 5px"> <span style="font-size: 12px; margin-left: 5px">
{{ userGroups.remainingGroups.length }} {{ userGroups.remainingGroups.length }}
<template v-if="currentUser.id === userDialog.id"> <template v-if="currentUser.id === userDialog.id">
@@ -943,9 +887,7 @@
</template> </template>
</template> </template>
</span> </span>
<div <div class="x-friend-list" style="margin-top: 10px; margin-bottom: 15px; min-height: 60px">
class="x-friend-list"
style="margin-top: 10px; margin-bottom: 15px; min-height: 60px">
<div <div
v-for="group in userGroups.remainingGroups" v-for="group in userGroups.remainingGroups"
:key="group.id" :key="group.id"
@@ -963,9 +905,7 @@
:content="t('dialog.group.members.representing')"> :content="t('dialog.group.members.representing')">
<Tag style="margin-right: 5px" /> <Tag style="margin-right: 5px" />
</TooltipWrapper> </TooltipWrapper>
<TooltipWrapper <TooltipWrapper v-if="group.memberVisibility !== 'visible'" side="top">
v-if="group.memberVisibility !== 'visible'"
side="top">
<template #content> <template #content>
<span <span
>{{ t('dialog.group.members.visibility') }} >{{ t('dialog.group.members.visibility') }}
@@ -1095,12 +1035,7 @@
v-slot:[String(index)]> v-slot:[String(index)]>
<div <div
class="x-friend-list" class="x-friend-list"
style=" style="margin-top: 10px; margin-bottom: 15px; min-height: 60px; max-height: none">
margin-top: 10px;
margin-bottom: 15px;
min-height: 60px;
max-height: none;
">
<div <div
v-for="world in list[2]" v-for="world in list[2]"
:key="world.favoriteId" :key="world.favoriteId"
@@ -1111,9 +1046,7 @@
</div> </div>
<div class="detail"> <div class="detail">
<span class="name" v-text="world.name"></span> <span class="name" v-text="world.name"></span>
<span v-if="world.occupants" class="extra" <span v-if="world.occupants" class="extra">({{ world.occupants }})</span>
>({{ world.occupants }})</span
>
</div> </div>
</div> </div>
</div> </div>
@@ -1163,14 +1096,10 @@
@update:modelValue="changeUserDialogAvatarSorting"> @update:modelValue="changeUserDialogAvatarSorting">
<SelectTrigger size="sm" @click.stop> <SelectTrigger size="sm" @click.stop>
<SelectValue <SelectValue
:placeholder=" :placeholder="t(`dialog.user.avatars.sort_by_${userDialog.avatarSorting}`)" />
t(`dialog.user.avatars.sort_by_${userDialog.avatarSorting}`)
" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="name">{{ <SelectItem value="name">{{ t('dialog.user.avatars.sort_by_name') }}</SelectItem>
t('dialog.user.avatars.sort_by_name')
}}</SelectItem>
<SelectItem value="update">{{ <SelectItem value="update">{{
t('dialog.user.avatars.sort_by_update') t('dialog.user.avatars.sort_by_update')
}}</SelectItem> }}</SelectItem>
@@ -1186,18 +1115,12 @@
@update:modelValue="(value) => (userDialog.avatarReleaseStatus = value)"> @update:modelValue="(value) => (userDialog.avatarReleaseStatus = value)">
<SelectTrigger size="sm" @click.stop> <SelectTrigger size="sm" @click.stop>
<SelectValue <SelectValue
:placeholder=" :placeholder="t(`dialog.user.avatars.${userDialog.avatarReleaseStatus}`)" />
t(`dialog.user.avatars.${userDialog.avatarReleaseStatus}`)
" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="all">{{ t('dialog.user.avatars.all') }}</SelectItem> <SelectItem value="all">{{ t('dialog.user.avatars.all') }}</SelectItem>
<SelectItem value="public">{{ <SelectItem value="public">{{ t('dialog.user.avatars.public') }}</SelectItem>
t('dialog.user.avatars.public') <SelectItem value="private">{{ t('dialog.user.avatars.private') }}</SelectItem>
}}</SelectItem>
<SelectItem value="private">{{
t('dialog.user.avatars.private')
}}</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</template> </template>
@@ -1210,17 +1133,11 @@
class="x-friend-item x-friend-item-border" class="x-friend-item x-friend-item-border"
@click="showAvatarDialog(avatar.id)"> @click="showAvatarDialog(avatar.id)">
<div class="avatar"> <div class="avatar">
<img <img v-if="avatar.thumbnailImageUrl" :src="avatar.thumbnailImageUrl" loading="lazy" />
v-if="avatar.thumbnailImageUrl"
:src="avatar.thumbnailImageUrl"
loading="lazy" />
</div> </div>
<div class="detail"> <div class="detail">
<span class="name" v-text="avatar.name"></span> <span class="name" v-text="avatar.name"></span>
<span <span v-if="avatar.releaseStatus === 'public'" class="extra" v-text="avatar.releaseStatus">
v-if="avatar.releaseStatus === 'public'"
class="extra"
v-text="avatar.releaseStatus">
</span> </span>
<span <span
v-else-if="avatar.releaseStatus === 'private'" v-else-if="avatar.releaseStatus === 'private'"
@@ -1234,11 +1151,7 @@
</template> </template>
<template #JSON> <template #JSON>
<Button <Button class="rounded-full mr-2" size="icon-sm" variant="outline" @click="refreshUserDialogTreeData()">
class="rounded-full mr-2"
size="icon-sm"
variant="outline"
@click="refreshUserDialogTreeData()">
<RefreshCw /> <RefreshCw />
</Button> </Button>
<Button <Button
@@ -1273,8 +1186,6 @@
<ModerateGroupDialog /> <ModerateGroupDialog />
<EditNoteAndMemoDialog v-model:visible="isEditNoteAndMemoDialogVisible" /> <EditNoteAndMemoDialog v-model:visible="isEditNoteAndMemoDialogVisible" />
</div> </div>
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
@@ -1295,7 +1206,6 @@
Tag, Tag,
Trash2 Trash2
} from 'lucide-vue-next'; } from 'lucide-vue-next';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { computed, defineAsyncComponent, nextTick, ref, watch } from 'vue'; import { computed, defineAsyncComponent, nextTick, ref, watch } from 'vue';
import { import {
@@ -1305,6 +1215,7 @@
DropdownMenuTrigger DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'; } from '@/components/ui/dropdown-menu';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { Spinner } from '@/components/ui/spinner'; import { Spinner } from '@/components/ui/spinner';
@@ -1,9 +1,5 @@
<template> <template>
<Dialog v-model:open="isDialogVisible"> <div>
<DialogContent
class="x-dialog x-world-dialog translate-y-0 sm:max-w-235"
:show-close-button="false"
style="top: 10vh">
<DialogHeader class="sr-only"> <DialogHeader class="sr-only">
<DialogTitle>{{ worldDialog.ref?.name || t('dialog.world.info.header') }}</DialogTitle> <DialogTitle>{{ worldDialog.ref?.name || t('dialog.world.info.header') }}</DialogTitle>
<DialogDescription> <DialogDescription>
@@ -21,10 +17,7 @@
<div style="flex: 1; display: flex; align-items: center; margin-left: 15px"> <div style="flex: 1; display: flex; align-items: center; margin-left: 15px">
<div style="flex: 1"> <div style="flex: 1">
<div> <div>
<span <span class="font-bold" style="margin-right: 5px; cursor: pointer" @click="copyWorldName">
class="font-bold"
style="margin-right: 5px; cursor: pointer"
@click="copyWorldName">
<Home <Home
v-if=" v-if="
currentUser.$homeLocation && currentUser.$homeLocation &&
@@ -93,12 +86,7 @@
<Apple class="h-4 w-4 text-[#8e8e93]" /> <Apple class="h-4 w-4 text-[#8e8e93]" />
<span <span
v-if="worldDialog.bundleSizes['ios']" v-if="worldDialog.bundleSizes['ios']"
:class="[ :class="['x-grey', 'x-tag-border-left', 'text-[#8e8e93]', 'border-[#8e8e93]']">
'x-grey',
'x-tag-border-left',
'text-[#8e8e93]',
'border-[#8e8e93]'
]">
{{ worldDialog.bundleSizes['ios'].fileSize }} {{ worldDialog.bundleSizes['ios'].fileSize }}
</span> </span>
</Badge> </Badge>
@@ -351,9 +339,7 @@
</div> </div>
<div v-for="room in worldDialog.rooms" :key="room.id"> <div v-for="room in worldDialog.rooms" :key="room.id">
<template <template
v-if=" v-if="isAgeGatedInstancesVisible || !(room.ageGate || room.location?.includes('~ageGate'))">
isAgeGatedInstancesVisible || !(room.ageGate || room.location?.includes('~ageGate'))
">
<div style="margin: 5px 0"> <div style="margin: 5px 0">
<div class="flex-align-center"> <div class="flex-align-center">
<LocationWorld <LocationWorld
@@ -483,9 +469,7 @@
class="x-friend-item" class="x-friend-item"
style="width: 350px" style="width: 350px"
@click=" @click="
openExternalLink( openExternalLink(`https://www.youtube.com/watch?v=${worldDialog.ref.previewYoutubeId}`)
`https://www.youtube.com/watch?v=${worldDialog.ref.previewYoutubeId}`
)
"> ">
<div class="detail"> <div class="detail">
<span class="name"> <span class="name">
@@ -503,8 +487,7 @@
</span> </span>
<span <span
v-if=" v-if="
worldDialog.ref.tags?.filter((tag) => tag.startsWith('author_tag')).length > worldDialog.ref.tags?.filter((tag) => tag.startsWith('author_tag')).length > 0
0
" "
class="extra"> class="extra">
{{ worldTags }} {{ worldTags }}
@@ -692,7 +675,6 @@
</template> </template>
</TabsUnderline> </TabsUnderline>
</div> </div>
</DialogContent>
<template v-if="isDialogVisible"> <template v-if="isDialogVisible">
<WorldAllowedDomainsDialog :world-allowed-domains-dialog="worldAllowedDomainsDialog" /> <WorldAllowedDomainsDialog :world-allowed-domains-dialog="worldAllowedDomainsDialog" />
@@ -709,7 +691,7 @@
v-model:change-world-image-dialog-visible="changeWorldImageDialogVisible" v-model:change-world-image-dialog-visible="changeWorldImageDialogVisible"
v-model:previousImageUrl="previousImageUrl" /> v-model:previousImageUrl="previousImageUrl" />
</template> </template>
</Dialog> </div>
</template> </template>
<script setup> <script setup>
@@ -737,8 +719,8 @@
User, User,
Wand2 Wand2
} from 'lucide-vue-next'; } from 'lucide-vue-next';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, defineAsyncComponent, nextTick, ref, watch } from 'vue'; import { computed, defineAsyncComponent, nextTick, ref, watch } from 'vue';
import { DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';
import { Spinner } from '@/components/ui/spinner'; import { Spinner } from '@/components/ui/spinner';
@@ -0,0 +1,11 @@
<script setup>
const props = defineProps({
class: { type: null, required: false }
});
</script>
<template>
<nav aria-label="breadcrumb" data-slot="breadcrumb" :class="props.class">
<slot />
</nav>
</template>
@@ -0,0 +1,21 @@
<script setup>
import { MoreHorizontal } from 'lucide-vue-next';
import { cn } from '@/lib/utils';
const props = defineProps({
class: { type: null, required: false }
});
</script>
<template>
<span
data-slot="breadcrumb-ellipsis"
role="presentation"
aria-hidden="true"
:class="cn('flex size-9 items-center justify-center', props.class)">
<slot>
<MoreHorizontal class="size-4" />
</slot>
<span class="sr-only">More</span>
</span>
</template>
@@ -0,0 +1,13 @@
<script setup>
import { cn } from '@/lib/utils';
const props = defineProps({
class: { type: null, required: false }
});
</script>
<template>
<li data-slot="breadcrumb-item" :class="cn('inline-flex items-center gap-1.5', props.class)">
<slot />
</li>
</template>
@@ -0,0 +1,20 @@
<script setup>
import { Primitive } from 'reka-ui';
import { cn } from '@/lib/utils';
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false, default: 'a' },
class: { type: null, required: false }
});
</script>
<template>
<Primitive
data-slot="breadcrumb-link"
:as="as"
:as-child="asChild"
:class="cn('hover:text-foreground transition-colors', props.class)">
<slot />
</Primitive>
</template>
@@ -0,0 +1,17 @@
<script setup>
import { cn } from '@/lib/utils';
const props = defineProps({
class: { type: null, required: false }
});
</script>
<template>
<ol
data-slot="breadcrumb-list"
:class="
cn('text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5', props.class)
">
<slot />
</ol>
</template>
@@ -0,0 +1,18 @@
<script setup>
import { cn } from '@/lib/utils';
const props = defineProps({
class: { type: null, required: false }
});
</script>
<template>
<span
data-slot="breadcrumb-page"
role="link"
aria-disabled="true"
aria-current="page"
:class="cn('text-foreground font-normal', props.class)">
<slot />
</span>
</template>
@@ -0,0 +1,20 @@
<script setup>
import { ChevronRight } from 'lucide-vue-next';
import { cn } from '@/lib/utils';
const props = defineProps({
class: { type: null, required: false }
});
</script>
<template>
<li
data-slot="breadcrumb-separator"
role="presentation"
aria-hidden="true"
:class="cn('[&>svg]:size-3.5', props.class)">
<slot>
<ChevronRight />
</slot>
</li>
</template>
+7
View File
@@ -0,0 +1,7 @@
export { default as Breadcrumb } from './Breadcrumb.vue';
export { default as BreadcrumbEllipsis } from './BreadcrumbEllipsis.vue';
export { default as BreadcrumbItem } from './BreadcrumbItem.vue';
export { default as BreadcrumbLink } from './BreadcrumbLink.vue';
export { default as BreadcrumbList } from './BreadcrumbList.vue';
export { default as BreadcrumbPage } from './BreadcrumbPage.vue';
export { default as BreadcrumbSeparator } from './BreadcrumbSeparator.vue';
@@ -5,12 +5,13 @@ import { useAppearanceSettingsStore } from '../stores';
import configRepository from '../service/config'; import configRepository from '../service/config';
export function useAuthenticatedLayoutResizable() { export function useMainLayoutResizable() {
const asideMaxPx = 500; const asideMaxPx = 500;
const appearanceStore = useAppearanceSettingsStore(); const appearanceStore = useAppearanceSettingsStore();
const { setAsideWidth } = appearanceStore; const { setAsideWidth } = appearanceStore;
const { asideWidth, isSideBarTabShow, isNavCollapsed } = storeToRefs(appearanceStore); const { asideWidth, isSideBarTabShow, isNavCollapsed } =
storeToRefs(appearanceStore);
const fallbackWidth = const fallbackWidth =
typeof window !== 'undefined' && window.innerWidth typeof window !== 'undefined' && window.innerWidth
@@ -63,7 +64,9 @@ export function useAuthenticatedLayoutResizable() {
const percentToPx = (percent, groupWidth) => (percent / 100) * groupWidth; const percentToPx = (percent, groupWidth) => (percent / 100) * groupWidth;
const isAsideCollapsed = (layout) => const isAsideCollapsed = (layout) =>
Array.isArray(layout) && layout.length >= 2 && layout[layout.length - 1] <= 1; Array.isArray(layout) &&
layout.length >= 2 &&
layout[layout.length - 1] <= 1;
const asideDefaultSize = computed(() => const asideDefaultSize = computed(() =>
pxToPercent(asideWidth.value, undefined, 0) pxToPercent(asideWidth.value, undefined, 0)
@@ -170,6 +173,7 @@ export function useAuthenticatedLayoutResizable() {
asidePanelRef, asidePanelRef,
asideDefaultSize, asideDefaultSize,
asideMaxSize, asideMaxSize,
asideMaxPx,
mainDefaultSize, mainDefaultSize,
handleLayout, handleLayout,
setIsDragging, setIsDragging,
+2 -2
View File
@@ -2,7 +2,7 @@ import { createRouter, createWebHashHistory } from 'vue-router';
import { watchState } from './../service/watchState'; import { watchState } from './../service/watchState';
import AuthenticatedLayout from '../views/Layout/AuthenticatedLayout.vue'; import MainLayout from '../views/Layout/MainLayout.vue';
import Charts from './../views/Charts/Charts.vue'; import Charts from './../views/Charts/Charts.vue';
import FavoritesAvatar from './../views/Favorites/FavoritesAvatar.vue'; import FavoritesAvatar from './../views/Favorites/FavoritesAvatar.vue';
import FavoritesFriend from './../views/Favorites/FavoritesFriend.vue'; import FavoritesFriend from './../views/Favorites/FavoritesFriend.vue';
@@ -31,7 +31,7 @@ const routes = [
}, },
{ {
path: '/', path: '/',
component: AuthenticatedLayout, component: MainLayout,
meta: { requiresAuth: true }, meta: { requiresAuth: true },
children: [ children: [
{ path: '', redirect: { name: 'feed' } }, { path: '', redirect: { name: 'feed' } },
+27 -1
View File
@@ -18,9 +18,12 @@ import { database } from '../service/database';
import { useAdvancedSettingsStore } from './settings/advanced'; import { useAdvancedSettingsStore } from './settings/advanced';
import { useAvatarProviderStore } from './avatarProvider'; import { useAvatarProviderStore } from './avatarProvider';
import { useFavoriteStore } from './favorite'; import { useFavoriteStore } from './favorite';
import { useGroupStore } from './group';
import { useModalStore } from './modal'; import { useModalStore } from './modal';
import { useUiStore } from './ui';
import { useUserStore } from './user'; import { useUserStore } from './user';
import { useVRCXUpdaterStore } from './vrcxUpdater'; import { useVRCXUpdaterStore } from './vrcxUpdater';
import { useWorldStore } from './world';
import { watchState } from '../service/watchState'; import { watchState } from '../service/watchState';
import webApiService from '../service/webapi'; import webApiService from '../service/webapi';
@@ -31,7 +34,10 @@ export const useAvatarStore = defineStore('Avatar', () => {
const vrcxUpdaterStore = useVRCXUpdaterStore(); const vrcxUpdaterStore = useVRCXUpdaterStore();
const advancedSettingsStore = useAdvancedSettingsStore(); const advancedSettingsStore = useAdvancedSettingsStore();
const userStore = useUserStore(); const userStore = useUserStore();
const worldStore = useWorldStore();
const groupStore = useGroupStore();
const modalStore = useModalStore(); const modalStore = useModalStore();
const uiStore = useUiStore();
const { t } = useI18n(); const { t } = useI18n();
let cachedAvatarModerations = new Map(); let cachedAvatarModerations = new Map();
@@ -172,8 +178,22 @@ export const useAvatarStore = defineStore('Avatar', () => {
* @param {string} avatarId * @param {string} avatarId
* @returns * @returns
*/ */
function showAvatarDialog(avatarId) { function showAvatarDialog(avatarId, options = {}) {
const D = avatarDialog.value; const D = avatarDialog.value;
if (
!avatarDialog.value.visible &&
!userStore.userDialog.visible &&
!worldStore.worldDialog.visible &&
!groupStore.groupDialog.visible
) {
uiStore.clearDialogCrumbs();
}
if (!options.skipBreadcrumb) {
uiStore.pushDialogCrumb('avatar', avatarId);
}
userStore.userDialog.visible = false;
worldStore.worldDialog.visible = false;
groupStore.groupDialog.visible = false;
D.visible = true; D.visible = true;
D.loading = true; D.loading = true;
D.id = avatarId; D.id = avatarId;
@@ -201,6 +221,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
const ref2 = cachedAvatars.get(avatarId); const ref2 = cachedAvatars.get(avatarId);
if (typeof ref2 !== 'undefined') { if (typeof ref2 !== 'undefined') {
D.ref = ref2; D.ref = ref2;
uiStore.setDialogCrumbLabel('avatar', D.id, D.ref?.name || D.id);
updateVRChatAvatarCache(); updateVRChatAvatarCache();
if ( if (
ref2.releaseStatus !== 'public' && ref2.releaseStatus !== 'public' &&
@@ -215,6 +236,11 @@ export const useAvatarStore = defineStore('Avatar', () => {
.then((args) => { .then((args) => {
const ref = applyAvatar(args.json); const ref = applyAvatar(args.json);
D.ref = ref; D.ref = ref;
uiStore.setDialogCrumbLabel(
'avatar',
D.id,
D.ref?.name || D.id
);
getAvatarGallery(avatarId); getAvatarGallery(avatarId);
updateVRChatAvatarCache(); updateVRChatAvatarCache();
if (/quest/.test(ref.tags)) { if (/quest/.test(ref.tags)) {
+26 -1
View File
@@ -16,11 +16,14 @@ import {
} from '../shared/utils'; } from '../shared/utils';
import { database } from '../service/database.js'; import { database } from '../service/database.js';
import { groupDialogFilterOptions } from '../shared/constants/'; import { groupDialogFilterOptions } from '../shared/constants/';
import { useAvatarStore } from './avatar';
import { useGameStore } from './game'; import { useGameStore } from './game';
import { useInstanceStore } from './instance'; import { useInstanceStore } from './instance';
import { useModalStore } from './modal'; import { useModalStore } from './modal';
import { useNotificationStore } from './notification'; import { useNotificationStore } from './notification';
import { useUiStore } from './ui';
import { useUserStore } from './user'; import { useUserStore } from './user';
import { useWorldStore } from './world';
import { watchState } from '../service/watchState'; import { watchState } from '../service/watchState';
import configRepository from '../service/config'; import configRepository from '../service/config';
@@ -31,8 +34,11 @@ export const useGroupStore = defineStore('Group', () => {
const instanceStore = useInstanceStore(); const instanceStore = useInstanceStore();
const gameStore = useGameStore(); const gameStore = useGameStore();
const userStore = useUserStore(); const userStore = useUserStore();
const worldStore = useWorldStore();
const avatarStore = useAvatarStore();
const notificationStore = useNotificationStore(); const notificationStore = useNotificationStore();
const modalStore = useModalStore(); const modalStore = useModalStore();
const uiStore = useUiStore();
const { t } = useI18n(); const { t } = useI18n();
let cachedGroups = new Map(); let cachedGroups = new Map();
@@ -124,10 +130,24 @@ export const useGroupStore = defineStore('Group', () => {
{ flush: 'sync' } { flush: 'sync' }
); );
function showGroupDialog(groupId) { function showGroupDialog(groupId, options = {}) {
if (!groupId) { if (!groupId) {
return; return;
} }
if (
!groupDialog.value.visible &&
!userStore.userDialog.visible &&
!worldStore.worldDialog.visible &&
!avatarStore.avatarDialog.visible
) {
uiStore.clearDialogCrumbs();
}
if (!options.skipBreadcrumb) {
uiStore.pushDialogCrumb('group', groupId);
}
userStore.userDialog.visible = false;
worldStore.worldDialog.visible = false;
avatarStore.avatarDialog.visible = false;
const D = groupDialog.value; const D = groupDialog.value;
D.visible = true; D.visible = true;
D.loading = true; D.loading = true;
@@ -161,6 +181,11 @@ export const useGroupStore = defineStore('Group', () => {
if (groupId === args.ref.id) { if (groupId === args.ref.id) {
D.loading = false; D.loading = false;
D.ref = args.ref; D.ref = args.ref;
uiStore.setDialogCrumbLabel(
'group',
D.id,
D.ref?.name || D.id
);
D.inGroup = args.ref.membershipStatus === 'member'; D.inGroup = args.ref.membershipStatus === 'member';
D.ownerDisplayName = args.ref.ownerId; D.ownerDisplayName = args.ref.ownerId;
userRequest userRequest
+55 -1
View File
@@ -27,6 +27,7 @@ export const useUiStore = defineStore('Ui', () => {
const notifiedMenus = ref([]); const notifiedMenus = ref([]);
const shiftHeld = ref(false); const shiftHeld = ref(false);
const trayIconNotify = ref(false); const trayIconNotify = ref(false);
const dialogCrumbs = ref([]);
watch(ctrlR, (isPressed) => { watch(ctrlR, (isPressed) => {
if (isPressed) { if (isPressed) {
@@ -58,6 +59,54 @@ export const useUiStore = defineStore('Ui', () => {
} }
}); });
function pushDialogCrumb(type, id, label = '') {
if (!type || !id) {
return;
}
const items = dialogCrumbs.value;
const last = items[items.length - 1];
if (last && last.type === type && last.id === id) {
if (label && last.label !== label) {
last.label = label;
}
return;
}
const existingIndex = items.findIndex(
(item) => item.type === type && item.id === id
);
if (existingIndex !== -1) {
items.splice(existingIndex + 1);
if (label) {
items[existingIndex].label = label;
}
return;
}
items.push({ type, id, label: label || id });
}
function setDialogCrumbLabel(type, id, label) {
if (!type || !id || !label) {
return;
}
const item = dialogCrumbs.value.find(
(entry) => entry.type === type && entry.id === id
);
if (item) {
item.label = label;
}
}
function jumpDialogCrumb(index) {
if (index < 0 || index >= dialogCrumbs.value.length) {
return;
}
dialogCrumbs.value.splice(index + 1);
}
function clearDialogCrumbs() {
dialogCrumbs.value = [];
}
// Make sure file drops outside of the screenshot manager don't navigate to the file path dropped. // Make sure file drops outside of the screenshot manager don't navigate to the file path dropped.
// This issue persists on prompts created with prompt(), unfortunately. Not sure how to fix that. // This issue persists on prompts created with prompt(), unfortunately. Not sure how to fix that.
document.body.addEventListener('drop', function (e) { document.body.addEventListener('drop', function (e) {
@@ -133,10 +182,15 @@ export const useUiStore = defineStore('Ui', () => {
return { return {
notifiedMenus, notifiedMenus,
shiftHeld, shiftHeld,
dialogCrumbs,
notifyMenu, notifyMenu,
removeNotify, removeNotify,
showConsole, showConsole,
updateTrayIconNotify updateTrayIconNotify,
pushDialogCrumb,
setDialogCrumbLabel,
jumpDialogCrumb,
clearDialogCrumbs
}; };
}); });
+23 -1
View File
@@ -48,6 +48,7 @@ import { useNotificationStore } from './notification';
import { usePhotonStore } from './photon'; import { usePhotonStore } from './photon';
import { useSearchStore } from './search'; import { useSearchStore } from './search';
import { useSharedFeedStore } from './sharedFeed'; import { useSharedFeedStore } from './sharedFeed';
import { useUiStore } from './ui';
import { useWorldStore } from './world'; import { useWorldStore } from './world';
import { watchState } from '../service/watchState'; import { watchState } from '../service/watchState';
@@ -68,6 +69,7 @@ export const useUserStore = defineStore('User', () => {
const groupStore = useGroupStore(); const groupStore = useGroupStore();
const feedStore = useFeedStore(); const feedStore = useFeedStore();
const worldStore = useWorldStore(); const worldStore = useWorldStore();
const uiStore = useUiStore();
const moderationStore = useModerationStore(); const moderationStore = useModerationStore();
const photonStore = usePhotonStore(); const photonStore = usePhotonStore();
const sharedFeedStore = useSharedFeedStore(); const sharedFeedStore = useSharedFeedStore();
@@ -310,6 +312,7 @@ export const useUserStore = defineStore('User', () => {
customUserTags.clear(); customUserTags.clear();
state.notes.clear(); state.notes.clear();
subsetOfLanguages.value = []; subsetOfLanguages.value = [];
uiStore.clearDialogCrumbs();
} }
}, },
{ flush: 'sync' } { flush: 'sync' }
@@ -755,7 +758,7 @@ export const useUserStore = defineStore('User', () => {
* *
* @param {string} userId * @param {string} userId
*/ */
function showUserDialog(userId) { function showUserDialog(userId, options = {}) {
if ( if (
!userId || !userId ||
typeof userId !== 'string' || typeof userId !== 'string' ||
@@ -763,6 +766,20 @@ export const useUserStore = defineStore('User', () => {
) { ) {
return; return;
} }
if (
!userDialog.value.visible &&
!worldStore.worldDialog.visible &&
!avatarStore.avatarDialog.visible &&
!groupStore.groupDialog.visible
) {
uiStore.clearDialogCrumbs();
}
if (!options.skipBreadcrumb) {
uiStore.pushDialogCrumb('user', userId);
}
worldStore.worldDialog.visible = false;
avatarStore.avatarDialog.visible = false;
groupStore.groupDialog.visible = false;
const D = userDialog.value; const D = userDialog.value;
D.id = userId; D.id = userId;
D.treeData = {}; D.treeData = {};
@@ -846,6 +863,11 @@ export const useUserStore = defineStore('User', () => {
if (args.ref.id === D.id) { if (args.ref.id === D.id) {
requestAnimationFrame(() => { requestAnimationFrame(() => {
D.ref = args.ref; D.ref = args.ref;
uiStore.setDialogCrumbLabel(
'user',
D.id,
D.ref?.displayName || D.id
);
D.friend = friendStore.friends.get(D.id); D.friend = friendStore.friends.get(D.id);
D.isFriend = Boolean(D.friend); D.isFriend = Boolean(D.friend);
D.note = String(D.ref.note || ''); D.note = String(D.ref.note || '');
+26 -1
View File
@@ -14,9 +14,12 @@ import {
} from '../shared/utils'; } from '../shared/utils';
import { instanceRequest, miscRequest, worldRequest } from '../api'; import { instanceRequest, miscRequest, worldRequest } from '../api';
import { database } from '../service/database'; import { database } from '../service/database';
import { useAvatarStore } from './avatar';
import { useFavoriteStore } from './favorite'; import { useFavoriteStore } from './favorite';
import { useGroupStore } from './group';
import { useInstanceStore } from './instance'; import { useInstanceStore } from './instance';
import { useLocationStore } from './location'; import { useLocationStore } from './location';
import { useUiStore } from './ui';
import { useUserStore } from './user'; import { useUserStore } from './user';
import { watchState } from '../service/watchState'; import { watchState } from '../service/watchState';
@@ -25,6 +28,9 @@ export const useWorldStore = defineStore('World', () => {
const favoriteStore = useFavoriteStore(); const favoriteStore = useFavoriteStore();
const instanceStore = useInstanceStore(); const instanceStore = useInstanceStore();
const userStore = useUserStore(); const userStore = useUserStore();
const avatarStore = useAvatarStore();
const groupStore = useGroupStore();
const uiStore = useUiStore();
const { t } = useI18n(); const { t } = useI18n();
const worldDialog = reactive({ const worldDialog = reactive({
@@ -71,12 +77,26 @@ export const useWorldStore = defineStore('World', () => {
* @param {string} tag * @param {string} tag
* @param {string} shortName * @param {string} shortName
*/ */
function showWorldDialog(tag, shortName = null) { function showWorldDialog(tag, shortName = null, options = {}) {
const D = worldDialog; const D = worldDialog;
const L = parseLocation(tag); const L = parseLocation(tag);
if (L.worldId === '') { if (L.worldId === '') {
return; return;
} }
if (
!worldDialog.visible &&
!userStore.userDialog.visible &&
!avatarStore.avatarDialog.visible &&
!groupStore.groupDialog.visible
) {
uiStore.clearDialogCrumbs();
}
if (!options.skipBreadcrumb) {
uiStore.pushDialogCrumb('world', L.worldId);
}
userStore.userDialog.visible = false;
avatarStore.avatarDialog.visible = false;
groupStore.groupDialog.visible = false;
L.shortName = shortName; L.shortName = shortName;
D.id = L.worldId; D.id = L.worldId;
D.$location = L; D.$location = L;
@@ -141,6 +161,11 @@ export const useWorldStore = defineStore('World', () => {
if (D.id === args.ref.id) { if (D.id === args.ref.id) {
D.loading = false; D.loading = false;
D.ref = args.ref; D.ref = args.ref;
uiStore.setDialogCrumbLabel(
'world',
D.id,
D.ref?.name || D.id
);
D.isFavorite = favoriteStore.getCachedFavoritesByObjectId( D.isFavorite = favoriteStore.getCachedFavoritesByObjectId(
D.id D.id
); );
@@ -18,10 +18,7 @@
<ResizablePanelGroup <ResizablePanelGroup
ref="panelGroupRef" ref="panelGroupRef"
direction="horizontal" direction="horizontal"
:class="[ :class="['group/main-layout flex-1 h-full min-w-0', { 'aside-collapsed': isAsideCollapsedStatic }]"
'group/main-layout flex-1 h-full min-w-0',
{ 'aside-collapsed': isAsideCollapsedStatic }
]"
@layout="handleLayout"> @layout="handleLayout">
<template #default="{ layout }"> <template #default="{ layout }">
<ResizablePanel :default-size="mainDefaultSize" :order="1"> <ResizablePanel :default-size="mainDefaultSize" :order="1">
@@ -46,7 +43,8 @@
:max-size="asideMaxSize" :max-size="asideMaxSize"
:collapsed-size="0" :collapsed-size="0"
collapsible collapsible
:order="2"> :order="2"
:style="{ maxWidth: `${asideMaxPx}px` }">
<Sidebar></Sidebar> <Sidebar></Sidebar>
</ResizablePanel> </ResizablePanel>
</template> </template>
@@ -56,13 +54,7 @@
</SidebarProvider> </SidebarProvider>
<!-- ## Dialogs ## --> <!-- ## Dialogs ## -->
<UserDialog></UserDialog> <MainDialogContainer></MainDialogContainer>
<WorldDialog></WorldDialog>
<AvatarDialog></AvatarDialog>
<GroupDialog></GroupDialog>
<GroupMemberModerationDialog></GroupMemberModerationDialog> <GroupMemberModerationDialog></GroupMemberModerationDialog>
@@ -102,16 +94,15 @@
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../../components/ui/resizable'; import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../../components/ui/resizable';
import { SidebarInset, SidebarProvider } from '../../components/ui/sidebar'; import { SidebarInset, SidebarProvider } from '../../components/ui/sidebar';
import { useAppearanceSettingsStore } from '../../stores'; import { useAppearanceSettingsStore } from '../../stores';
import { useAuthenticatedLayoutResizable } from '../../composables/useAuthenticatedLayoutResizable'; import { useMainLayoutResizable } from '../../composables/useMainLayoutResizable';
import { watchState } from '../../service/watchState'; import { watchState } from '../../service/watchState';
import AvatarDialog from '../../components/dialogs/AvatarDialog/AvatarDialog.vue';
import AvatarImportDialog from '../Favorites/dialogs/AvatarImportDialog.vue'; import AvatarImportDialog from '../Favorites/dialogs/AvatarImportDialog.vue';
import ChangelogDialog from '../Settings/dialogs/ChangelogDialog.vue'; import ChangelogDialog from '../Settings/dialogs/ChangelogDialog.vue';
import ChooseFavoriteGroupDialog from '../../components/dialogs/ChooseFavoriteGroupDialog.vue'; import ChooseFavoriteGroupDialog from '../../components/dialogs/ChooseFavoriteGroupDialog.vue';
import MainDialogContainer from '../../components/dialogs/MainDialogContainer.vue';
import FriendImportDialog from '../Favorites/dialogs/FriendImportDialog.vue'; import FriendImportDialog from '../Favorites/dialogs/FriendImportDialog.vue';
import FullscreenImagePreview from '../../components/FullscreenImagePreview.vue'; import FullscreenImagePreview from '../../components/FullscreenImagePreview.vue';
import GroupDialog from '../../components/dialogs/GroupDialog/GroupDialog.vue';
import GroupMemberModerationDialog from '../../components/dialogs/GroupDialog/GroupMemberModerationDialog.vue'; import GroupMemberModerationDialog from '../../components/dialogs/GroupDialog/GroupMemberModerationDialog.vue';
import InviteGroupDialog from '../../components/dialogs/InviteGroupDialog.vue'; import InviteGroupDialog from '../../components/dialogs/InviteGroupDialog.vue';
import LaunchDialog from '../../components/dialogs/LaunchDialog.vue'; import LaunchDialog from '../../components/dialogs/LaunchDialog.vue';
@@ -121,9 +112,7 @@
import PrimaryPasswordDialog from '../Settings/dialogs/PrimaryPasswordDialog.vue'; import PrimaryPasswordDialog from '../Settings/dialogs/PrimaryPasswordDialog.vue';
import SendBoopDialog from '../../components/dialogs/SendBoopDialog.vue'; import SendBoopDialog from '../../components/dialogs/SendBoopDialog.vue';
import Sidebar from '../Sidebar/Sidebar.vue'; import Sidebar from '../Sidebar/Sidebar.vue';
import UserDialog from '../../components/dialogs/UserDialog/UserDialog.vue';
import VRChatConfigDialog from '../Settings/dialogs/VRChatConfigDialog.vue'; import VRChatConfigDialog from '../Settings/dialogs/VRChatConfigDialog.vue';
import WorldDialog from '../../components/dialogs/WorldDialog/WorldDialog.vue';
import WorldImportDialog from '../Favorites/dialogs/WorldImportDialog.vue'; import WorldImportDialog from '../Favorites/dialogs/WorldImportDialog.vue';
const router = useRouter(); const router = useRouter();
@@ -180,16 +169,15 @@
const { const {
asideDefaultSize, asideDefaultSize,
asideMaxSize, asideMaxSize,
asideMaxPx,
mainDefaultSize, mainDefaultSize,
handleLayout, handleLayout,
setIsDragging, setIsDragging,
isAsideCollapsed, isAsideCollapsed,
isSideBarTabShow isSideBarTabShow
} = useAuthenticatedLayoutResizable(); } = useMainLayoutResizable();
const isAsideCollapsedStatic = computed( const isAsideCollapsedStatic = computed(() => !isSideBarTabShow.value || asideWidth.value === 0);
() => !isSideBarTabShow.value || asideWidth.value === 0
);
watch( watch(
() => watchState.isLoggedIn, () => watchState.isLoggedIn,