Files
VRCX/src/components/dialogs/MainDialogContainer.vue
2026-03-20 13:34:29 +09:00

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>