mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-13 11:53:51 +02:00
258 lines
12 KiB
Vue
258 lines
12 KiB
Vue
<script setup>
|
|
import {
|
|
Breadcrumb,
|
|
BreadcrumbEllipsis,
|
|
BreadcrumbItem,
|
|
BreadcrumbLink,
|
|
BreadcrumbList,
|
|
BreadcrumbPage,
|
|
BreadcrumbSeparator
|
|
} from '@/components/ui/breadcrumb';
|
|
import { useAvatarStore, useGroupStore, useInstanceStore, useUiStore, useUserStore, useWorldStore } from '@/stores';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger
|
|
} from '@/components/ui/dropdown-menu';
|
|
import { Dialog, DialogContent } from '@/components/ui/dialog';
|
|
import { ArrowLeft } from 'lucide-vue-next';
|
|
import { Button } from '@/components/ui/button';
|
|
import { TooltipWrapper } from '@/components/ui/tooltip';
|
|
import { computed } from 'vue';
|
|
import { storeToRefs } from 'pinia';
|
|
|
|
import AvatarDialog from './AvatarDialog/AvatarDialog.vue';
|
|
import GroupDialog from './GroupDialog/GroupDialog.vue';
|
|
import PreviousInstancesInfoDialog from './PreviousInstancesDialog/PreviousInstancesInfoDialog.vue';
|
|
import PreviousInstancesListDialog from './PreviousInstancesDialog/PreviousInstancesListDialog.vue';
|
|
import UserDialog from './UserDialog/UserDialog.vue';
|
|
import WorldDialog from './WorldDialog/WorldDialog.vue';
|
|
|
|
const avatarStore = useAvatarStore();
|
|
const groupStore = useGroupStore();
|
|
const instanceStore = useInstanceStore();
|
|
const uiStore = useUiStore();
|
|
const userStore = useUserStore();
|
|
const worldStore = useWorldStore();
|
|
|
|
const { previousInstancesInfoDialog, previousInstancesListDialog } = storeToRefs(instanceStore);
|
|
|
|
const dialogCrumbs = computed(() => uiStore.dialogCrumbs);
|
|
const activeType = computed(() => {
|
|
const type = (() => {
|
|
if (previousInstancesInfoDialog.value.visible) {
|
|
return 'previous-instances-info';
|
|
}
|
|
if (previousInstancesListDialog.value.visible) {
|
|
return `previous-instances-${previousInstancesListDialog.value.variant}`;
|
|
}
|
|
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;
|
|
})();
|
|
return type;
|
|
});
|
|
const activeComponent = computed(() => {
|
|
switch (activeType.value) {
|
|
case 'user':
|
|
return UserDialog;
|
|
case 'world':
|
|
return WorldDialog;
|
|
case 'avatar':
|
|
return AvatarDialog;
|
|
case 'group':
|
|
return GroupDialog;
|
|
case 'previous-instances-info':
|
|
return PreviousInstancesInfoDialog;
|
|
case 'previous-instances-user':
|
|
return PreviousInstancesListDialog;
|
|
case 'previous-instances-world':
|
|
return PreviousInstancesListDialog;
|
|
case 'previous-instances-group':
|
|
return PreviousInstancesListDialog;
|
|
default:
|
|
return null;
|
|
}
|
|
});
|
|
const activeComponentProps = computed(() => {
|
|
switch (activeType.value) {
|
|
case 'previous-instances-user':
|
|
return { variant: 'user' };
|
|
case 'previous-instances-world':
|
|
return { variant: 'world' };
|
|
case 'previous-instances-group':
|
|
return { variant: 'group' };
|
|
default:
|
|
return {};
|
|
}
|
|
});
|
|
const isOpen = computed({
|
|
get: () => activeComponent.value !== null,
|
|
set: (value) => {
|
|
if (!value) {
|
|
uiStore.closeMainDialog();
|
|
}
|
|
}
|
|
});
|
|
|
|
const dialogClass = computed(() => {
|
|
switch (activeType.value) {
|
|
case 'world':
|
|
return 'x-dialog translate-y-0 sm:max-w-235 overflow-hidden flex flex-col';
|
|
case 'avatar':
|
|
return 'x-dialog sm:max-w-235 translate-y-0 overflow-hidden flex flex-col';
|
|
case 'group':
|
|
return 'x-dialog translate-y-0 sm:max-w-235 overflow-hidden flex flex-col';
|
|
case 'previous-instances-info':
|
|
case 'previous-instances-user':
|
|
case 'previous-instances-world':
|
|
case 'previous-instances-group':
|
|
return 'x-dialog translate-y-0 sm:max-w-250';
|
|
case 'user':
|
|
default:
|
|
return 'x-dialog sm:max-w-235 translate-y-0 overflow-hidden flex flex-col';
|
|
}
|
|
});
|
|
|
|
const shouldShowBreadcrumbs = computed(() => dialogCrumbs.value.length > 1);
|
|
const shouldCollapseBreadcrumbs = computed(() => dialogCrumbs.value.length > 5);
|
|
const middleBreadcrumbs = computed(() => {
|
|
if (!shouldCollapseBreadcrumbs.value) {
|
|
return [];
|
|
}
|
|
return dialogCrumbs.value.slice(1, -2);
|
|
});
|
|
const backCrumbLabel = computed(() => {
|
|
if (dialogCrumbs.value.length < 2) {
|
|
return '';
|
|
}
|
|
const backCrumb = dialogCrumbs.value[dialogCrumbs.value.length - 2];
|
|
return backCrumb?.label || backCrumb?.id || '';
|
|
});
|
|
|
|
function handleBreadcrumbClick(index) {
|
|
uiStore.handleBreadcrumbClick(index);
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Dialog v-if="isOpen" v-model:open="isOpen">
|
|
<DialogContent :class="dialogClass" style="top: 10vh" :show-close-button="false">
|
|
<Breadcrumb v-if="shouldShowBreadcrumbs" class="mb-2 flex-shrink-0">
|
|
<BreadcrumbList>
|
|
<TooltipWrapper :content="backCrumbLabel" :disabled="!backCrumbLabel" :delayDuration="500">
|
|
<Button variant="ghost" size="icon-sm" @click="handleBreadcrumbClick(dialogCrumbs.length - 2)">
|
|
<ArrowLeft />
|
|
<span class="sr-only">{{ backCrumbLabel }}</span>
|
|
</Button>
|
|
</TooltipWrapper>
|
|
<template v-if="shouldCollapseBreadcrumbs">
|
|
<BreadcrumbItem>
|
|
<TooltipWrapper
|
|
:content="dialogCrumbs[0]?.label || dialogCrumbs[0]?.id"
|
|
:delayDuration="500">
|
|
<BreadcrumbLink as-child>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="sm"
|
|
class="max-w-40 justify-start truncate text-left"
|
|
@click="handleBreadcrumbClick(0)">
|
|
{{ dialogCrumbs[0]?.label || dialogCrumbs[0]?.id }}
|
|
</Button>
|
|
</BreadcrumbLink>
|
|
</TooltipWrapper>
|
|
</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>
|
|
<TooltipWrapper
|
|
:content="
|
|
dialogCrumbs[dialogCrumbs.length - 2]?.label ||
|
|
dialogCrumbs[dialogCrumbs.length - 2]?.id
|
|
"
|
|
:delayDuration="500">
|
|
<BreadcrumbLink as-child>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="sm"
|
|
class="max-w-40 justify-start truncate text-left"
|
|
@click="handleBreadcrumbClick(dialogCrumbs.length - 2)">
|
|
{{
|
|
dialogCrumbs[dialogCrumbs.length - 2]?.label ||
|
|
dialogCrumbs[dialogCrumbs.length - 2]?.id
|
|
}}
|
|
</Button>
|
|
</BreadcrumbLink>
|
|
</TooltipWrapper>
|
|
</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>
|
|
<TooltipWrapper
|
|
v-if="index < dialogCrumbs.length - 1"
|
|
:content="crumb.label || crumb.id"
|
|
:delayDuration="500">
|
|
<BreadcrumbLink as-child>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="sm"
|
|
class="max-w-40 justify-start truncate text-left"
|
|
@click="handleBreadcrumbClick(index)">
|
|
{{ crumb.label || crumb.id }}
|
|
</Button>
|
|
</BreadcrumbLink>
|
|
</TooltipWrapper>
|
|
<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" v-bind="activeComponentProps" :key="activeType" />
|
|
</DialogContent>
|
|
</Dialog>
|
|
</template>
|