mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-07 06:56:04 +02:00
feat: add breadcrumb components and main dialog layout functionality
This commit is contained in:
@@ -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>, </span></template
|
><span>, </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>
|
||||||
@@ -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>
|
||||||
@@ -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';
|
||||||
+7
-3
@@ -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,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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||||
Reference in New Issue
Block a user