mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-14 20:33:52 +02:00
861 lines
42 KiB
Vue
861 lines
42 KiB
Vue
<template>
|
|
<Dialog v-model:open="newInstanceDialog.visible">
|
|
<DialogContent class="sm:max-w-2xl">
|
|
<DialogHeader>
|
|
<DialogTitle>{{ t('dialog.new_instance.header') }}</DialogTitle>
|
|
<DialogDescription class="sr-only">{{ t('dialog.new_instance.header') }}</DialogDescription>
|
|
</DialogHeader>
|
|
<TabsUnderline
|
|
v-model="newInstanceDialog.selectedTab"
|
|
:items="newInstanceTabs"
|
|
:unmount-on-hide="false"
|
|
@update:modelValue="newInstanceTabClick">
|
|
<template #Normal>
|
|
<FieldGroup class="gap-4">
|
|
<Field>
|
|
<FieldLabel>{{ t('dialog.new_instance.access_type') }}</FieldLabel>
|
|
<FieldContent>
|
|
<ToggleGroup
|
|
type="single"
|
|
required
|
|
variant="outline"
|
|
size="sm"
|
|
:model-value="newInstanceDialog.accessType"
|
|
@update:model-value="
|
|
(value) => {
|
|
newInstanceDialog.accessType = value;
|
|
buildInstance();
|
|
}
|
|
">
|
|
<ToggleGroupItem value="public">{{
|
|
t('dialog.new_instance.access_type_public')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="group">{{
|
|
t('dialog.new_instance.access_type_group')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="friends+">{{
|
|
t('dialog.new_instance.access_type_friend_plus')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="friends">{{
|
|
t('dialog.new_instance.access_type_friend')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="invite+">{{
|
|
t('dialog.new_instance.access_type_invite_plus')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="invite">{{
|
|
t('dialog.new_instance.access_type_invite')
|
|
}}</ToggleGroupItem>
|
|
</ToggleGroup>
|
|
</FieldContent>
|
|
</Field>
|
|
<Field v-if="newInstanceDialog.accessType === 'group'">
|
|
<FieldLabel>{{ t('dialog.new_instance.group_access_type') }}</FieldLabel>
|
|
<FieldContent>
|
|
<ToggleGroup
|
|
type="single"
|
|
required
|
|
variant="outline"
|
|
size="sm"
|
|
:model-value="newInstanceDialog.groupAccessType"
|
|
@update:model-value="
|
|
(value) => {
|
|
newInstanceDialog.groupAccessType = value;
|
|
buildInstance();
|
|
}
|
|
">
|
|
<ToggleGroupItem
|
|
value="members"
|
|
:disabled="
|
|
!hasGroupPermission(
|
|
newInstanceDialog.groupRef,
|
|
'group-instance-open-create'
|
|
)
|
|
"
|
|
>{{ t('dialog.new_instance.group_access_type_members') }}</ToggleGroupItem
|
|
>
|
|
<ToggleGroupItem
|
|
value="plus"
|
|
:disabled="
|
|
!hasGroupPermission(
|
|
newInstanceDialog.groupRef,
|
|
'group-instance-plus-create'
|
|
)
|
|
"
|
|
>{{ t('dialog.new_instance.group_access_type_plus') }}</ToggleGroupItem
|
|
>
|
|
<ToggleGroupItem
|
|
value="public"
|
|
:disabled="
|
|
!hasGroupPermission(
|
|
newInstanceDialog.groupRef,
|
|
'group-instance-public-create'
|
|
) || newInstanceDialog.groupRef.privacy === 'private'
|
|
"
|
|
>{{ t('dialog.new_instance.group_access_type_public') }}</ToggleGroupItem
|
|
>
|
|
</ToggleGroup>
|
|
</FieldContent>
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel>{{ t('dialog.new_instance.region') }}</FieldLabel>
|
|
<FieldContent>
|
|
<ToggleGroup
|
|
type="single"
|
|
required
|
|
variant="outline"
|
|
size="sm"
|
|
:model-value="newInstanceDialog.region"
|
|
@update:model-value="
|
|
(value) => {
|
|
newInstanceDialog.region = value;
|
|
buildInstance();
|
|
}
|
|
">
|
|
<ToggleGroupItem value="US West">{{
|
|
t('dialog.new_instance.region_usw')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="US East">{{
|
|
t('dialog.new_instance.region_use')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="Europe">{{
|
|
t('dialog.new_instance.region_eu')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="Japan">{{
|
|
t('dialog.new_instance.region_jp')
|
|
}}</ToggleGroupItem>
|
|
</ToggleGroup>
|
|
</FieldContent>
|
|
</Field>
|
|
<Field v-if="newInstanceDialog.accessType === 'group'">
|
|
<FieldLabel>{{ t('dialog.new_instance.queueEnabled') }}</FieldLabel>
|
|
<FieldContent>
|
|
<Checkbox v-model="newInstanceDialog.queueEnabled" @update:modelValue="buildInstance" />
|
|
</FieldContent>
|
|
</Field>
|
|
<Field v-if="newInstanceDialog.accessType === 'group'">
|
|
<FieldLabel>{{ t('dialog.new_instance.ageGate') }}</FieldLabel>
|
|
<FieldContent>
|
|
<Checkbox
|
|
v-model="newInstanceDialog.ageGate"
|
|
:disabled="
|
|
!hasGroupPermission(
|
|
newInstanceDialog.groupRef,
|
|
'group-instance-age-gated-create'
|
|
)
|
|
"
|
|
@update:modelValue="buildInstance" />
|
|
</FieldContent>
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel>{{ t('dialog.new_instance.display_name') }}</FieldLabel>
|
|
<FieldContent>
|
|
<InputGroupField
|
|
:disabled="!isLocalUserVrcPlusSupporter"
|
|
v-model="newInstanceDialog.displayName"
|
|
size="sm"
|
|
@click="$event.target.tagName === 'INPUT' && $event.target.select()"
|
|
@change="buildInstance" />
|
|
</FieldContent>
|
|
</Field>
|
|
<Field v-if="newInstanceDialog.accessType === 'group'">
|
|
<FieldLabel>{{ t('dialog.new_instance.group_id') }}</FieldLabel>
|
|
<FieldContent>
|
|
<VirtualCombobox
|
|
v-model="newInstanceDialog.groupId"
|
|
:groups="normalGroupPickerGroups"
|
|
:placeholder="t('dialog.new_instance.group_placeholder')"
|
|
:search-placeholder="t('dialog.new_instance.group_placeholder')"
|
|
:clearable="true"
|
|
:close-on-select="true"
|
|
:deselect-on-reselect="true"
|
|
@change="buildInstance">
|
|
<template #item="{ item, selected }">
|
|
<div class="flex w-full items-center p-1.5 text-[13px]">
|
|
<div class="relative inline-block flex-none size-9 mr-2.5">
|
|
<img
|
|
class="size-full rounded-full object-cover"
|
|
:src="item.iconUrl"
|
|
loading="lazy" />
|
|
</div>
|
|
<div class="flex-1 overflow-hidden">
|
|
<span
|
|
class="block truncate font-medium leading-[18px]"
|
|
v-text="item.label"></span>
|
|
</div>
|
|
<CheckIcon
|
|
:class="['ml-auto size-4', selected ? 'opacity-100' : 'opacity-0']" />
|
|
</div>
|
|
</template>
|
|
</VirtualCombobox>
|
|
</FieldContent>
|
|
</Field>
|
|
<Field
|
|
v-if="
|
|
newInstanceDialog.accessType === 'group' &&
|
|
newInstanceDialog.groupAccessType === 'members'
|
|
"
|
|
class="items-start">
|
|
<FieldLabel>{{ t('dialog.new_instance.roles') }}</FieldLabel>
|
|
<FieldContent>
|
|
<Select
|
|
multiple
|
|
:model-value="
|
|
Array.isArray(newInstanceDialog.roleIds) ? newInstanceDialog.roleIds : []
|
|
"
|
|
@update:modelValue="handleRoleIdsChange">
|
|
<SelectTrigger size="sm" class="w-full">
|
|
<SelectValue>
|
|
<span class="truncate">
|
|
{{ selectedRoleSummary || t('dialog.new_instance.role_placeholder') }}
|
|
</span>
|
|
</SelectValue>
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectGroup>
|
|
<SelectItem
|
|
v-for="role in newInstanceDialog.selectedGroupRoles"
|
|
:key="role.id"
|
|
:value="role.id">
|
|
{{ role.name }}
|
|
</SelectItem>
|
|
</SelectGroup>
|
|
</SelectContent>
|
|
</Select>
|
|
</FieldContent>
|
|
</Field>
|
|
<template v-if="newInstanceDialog.instanceCreated">
|
|
<Field>
|
|
<FieldLabel>{{ t('dialog.new_instance.location') }}</FieldLabel>
|
|
<FieldContent>
|
|
<InputGroupField
|
|
v-model="newInstanceDialog.location"
|
|
size="sm"
|
|
readonly
|
|
@click="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
|
</FieldContent>
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel>{{ t('dialog.new_instance.url') }}</FieldLabel>
|
|
<FieldContent>
|
|
<InputGroupField v-model="newInstanceDialog.url" size="sm" readonly />
|
|
</FieldContent>
|
|
</Field>
|
|
</template>
|
|
</FieldGroup>
|
|
</template>
|
|
<template #Legacy>
|
|
<FieldGroup class="gap-4">
|
|
<Field>
|
|
<FieldLabel>{{ t('dialog.new_instance.access_type') }}</FieldLabel>
|
|
<FieldContent>
|
|
<ToggleGroup
|
|
type="single"
|
|
required
|
|
variant="outline"
|
|
size="sm"
|
|
:model-value="newInstanceDialog.accessType"
|
|
@update:model-value="
|
|
(value) => {
|
|
newInstanceDialog.accessType = value;
|
|
buildLegacyInstance();
|
|
}
|
|
">
|
|
<ToggleGroupItem value="public">{{
|
|
t('dialog.new_instance.access_type_public')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="group">{{
|
|
t('dialog.new_instance.access_type_group')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="friends+">{{
|
|
t('dialog.new_instance.access_type_friend_plus')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="friends">{{
|
|
t('dialog.new_instance.access_type_friend')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="invite+">{{
|
|
t('dialog.new_instance.access_type_invite_plus')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="invite">{{
|
|
t('dialog.new_instance.access_type_invite')
|
|
}}</ToggleGroupItem>
|
|
</ToggleGroup>
|
|
</FieldContent>
|
|
</Field>
|
|
<Field v-if="newInstanceDialog.accessType === 'group'">
|
|
<FieldLabel>{{ t('dialog.new_instance.group_access_type') }}</FieldLabel>
|
|
<FieldContent>
|
|
<ToggleGroup
|
|
type="single"
|
|
required
|
|
variant="outline"
|
|
size="sm"
|
|
:model-value="newInstanceDialog.groupAccessType"
|
|
@update:model-value="
|
|
(value) => {
|
|
newInstanceDialog.groupAccessType = value;
|
|
buildLegacyInstance();
|
|
}
|
|
">
|
|
<ToggleGroupItem value="members">{{
|
|
t('dialog.new_instance.group_access_type_members')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="plus">{{
|
|
t('dialog.new_instance.group_access_type_plus')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="public">{{
|
|
t('dialog.new_instance.group_access_type_public')
|
|
}}</ToggleGroupItem>
|
|
</ToggleGroup>
|
|
</FieldContent>
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel>{{ t('dialog.new_instance.region') }}</FieldLabel>
|
|
<FieldContent>
|
|
<ToggleGroup
|
|
type="single"
|
|
required
|
|
variant="outline"
|
|
size="sm"
|
|
:model-value="newInstanceDialog.region"
|
|
@update:model-value="
|
|
(value) => {
|
|
newInstanceDialog.region = value;
|
|
buildLegacyInstance();
|
|
}
|
|
">
|
|
<ToggleGroupItem value="US West">{{
|
|
t('dialog.new_instance.region_usw')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="US East">{{
|
|
t('dialog.new_instance.region_use')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="Europe">{{
|
|
t('dialog.new_instance.region_eu')
|
|
}}</ToggleGroupItem>
|
|
<ToggleGroupItem value="Japan">{{
|
|
t('dialog.new_instance.region_jp')
|
|
}}</ToggleGroupItem>
|
|
</ToggleGroup>
|
|
</FieldContent>
|
|
</Field>
|
|
<Field v-if="newInstanceDialog.accessType === 'group'">
|
|
<FieldLabel>{{ t('dialog.new_instance.ageGate') }}</FieldLabel>
|
|
<FieldContent>
|
|
<Checkbox v-model="newInstanceDialog.ageGate" @update:modelValue="buildInstance" />
|
|
</FieldContent>
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel>{{ t('dialog.new_instance.world_id') }}</FieldLabel>
|
|
<FieldContent>
|
|
<InputGroupField
|
|
v-model="newInstanceDialog.worldId"
|
|
size="sm"
|
|
@click="$event.target.tagName === 'INPUT' && $event.target.select()"
|
|
@change="buildLegacyInstance" />
|
|
</FieldContent>
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel>{{ t('dialog.new_instance.instance_id') }}</FieldLabel>
|
|
<FieldContent>
|
|
<InputGroupField
|
|
v-model="newInstanceDialog.instanceName"
|
|
:placeholder="t('dialog.new_instance.instance_id_placeholder')"
|
|
size="sm"
|
|
@change="buildLegacyInstance" />
|
|
</FieldContent>
|
|
</Field>
|
|
<Field
|
|
v-if="
|
|
newInstanceDialog.selectedTab === 'Legacy' &&
|
|
newInstanceDialog.accessType !== 'public' &&
|
|
newInstanceDialog.accessType !== 'group'
|
|
"
|
|
class="items-start">
|
|
<FieldLabel>{{ t('dialog.new_instance.instance_creator') }}</FieldLabel>
|
|
<FieldContent>
|
|
<VirtualCombobox
|
|
v-model="newInstanceDialog.legacyUserId"
|
|
:groups="creatorPickerGroups"
|
|
:placeholder="t('dialog.new_instance.instance_creator_placeholder')"
|
|
:search-placeholder="t('dialog.new_instance.instance_creator_placeholder')"
|
|
:clearable="true"
|
|
:close-on-select="true"
|
|
:deselect-on-reselect="true"
|
|
@change="buildLegacyInstance">
|
|
<template #item="{ item, selected }">
|
|
<div class="flex w-full items-center p-1.5 text-[13px]">
|
|
<template v-if="item.user">
|
|
<div
|
|
class="relative inline-block flex-none size-9 mr-2.5"
|
|
:class="userStatusClass(item.user)">
|
|
<img
|
|
class="size-full rounded-full object-cover"
|
|
:src="userImage(item.user)"
|
|
loading="lazy" />
|
|
</div>
|
|
<div class="flex-1 overflow-hidden">
|
|
<span
|
|
class="block truncate font-medium leading-[18px]"
|
|
:style="{ color: item.user.$userColour }"
|
|
v-text="item.user.displayName"></span>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
<span v-text="item.label"></span>
|
|
</template>
|
|
|
|
<CheckIcon
|
|
:class="['ml-auto size-4', selected ? 'opacity-100' : 'opacity-0']" />
|
|
</div>
|
|
</template>
|
|
</VirtualCombobox>
|
|
</FieldContent>
|
|
</Field>
|
|
<Field v-if="newInstanceDialog.accessType === 'group'">
|
|
<FieldLabel>{{ t('dialog.new_instance.group_id') }}</FieldLabel>
|
|
<FieldContent>
|
|
<VirtualCombobox
|
|
v-model="newInstanceDialog.groupId"
|
|
:groups="legacyGroupPickerGroups"
|
|
:placeholder="t('dialog.new_instance.group_placeholder')"
|
|
:search-placeholder="t('dialog.new_instance.group_placeholder')"
|
|
:clearable="true"
|
|
:close-on-select="true"
|
|
:deselect-on-reselect="true"
|
|
@change="buildLegacyInstance">
|
|
<template #item="{ item, selected }">
|
|
<div class="flex w-full items-center p-1.5 text-[13px]">
|
|
<div class="relative inline-block flex-none size-9 mr-2.5">
|
|
<img
|
|
class="size-full rounded-full object-cover"
|
|
:src="item.iconUrl"
|
|
loading="lazy" />
|
|
</div>
|
|
<div class="flex-1 overflow-hidden">
|
|
<span
|
|
class="block truncate font-medium leading-[18px]"
|
|
v-text="item.label"></span>
|
|
</div>
|
|
<CheckIcon
|
|
:class="['ml-auto size-4', selected ? 'opacity-100' : 'opacity-0']" />
|
|
</div>
|
|
</template>
|
|
</VirtualCombobox>
|
|
</FieldContent>
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel>{{ t('dialog.new_instance.location') }}</FieldLabel>
|
|
<FieldContent>
|
|
<InputGroupField
|
|
v-model="newInstanceDialog.location"
|
|
size="sm"
|
|
readonly
|
|
@click="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
|
</FieldContent>
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel>{{ t('dialog.new_instance.url') }}</FieldLabel>
|
|
<FieldContent>
|
|
<InputGroupField v-model="newInstanceDialog.url" size="sm" readonly />
|
|
</FieldContent>
|
|
</Field>
|
|
</FieldGroup>
|
|
</template>
|
|
</TabsUnderline>
|
|
<DialogFooter v-if="newInstanceDialog.selectedTab === 'Normal'">
|
|
<template v-if="newInstanceDialog.instanceCreated">
|
|
<Button variant="outline" class="mr-2" @click="copyInstanceUrl(newInstanceDialog.location)">{{
|
|
t('dialog.new_instance.copy_url')
|
|
}}</Button>
|
|
<Button variant="outline" class="mr-2" @click="selfInvite(newInstanceDialog.location)">{{
|
|
t('dialog.new_instance.self_invite')
|
|
}}</Button>
|
|
<Button
|
|
variant="outline"
|
|
class="mr-2"
|
|
:disabled="
|
|
(newInstanceDialog.accessType === 'friends' || newInstanceDialog.accessType === 'invite') &&
|
|
newInstanceDialog.userId !== currentUser.id
|
|
"
|
|
@click="showInviteDialog(newInstanceDialog.location)"
|
|
>{{ t('dialog.new_instance.invite') }}</Button
|
|
>
|
|
<template v-if="canOpenInstanceInGame">
|
|
<Button
|
|
variant="secondary"
|
|
class="mr-2"
|
|
@click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)"
|
|
>{{ t('dialog.new_instance.launch') }}</Button
|
|
>
|
|
<Button @click="handleAttachGame(newInstanceDialog.location, newInstanceDialog.shortName)">
|
|
{{ t('dialog.new_instance.open_ingame') }}
|
|
</Button>
|
|
</template>
|
|
<template v-else>
|
|
<Button @click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)">{{
|
|
t('dialog.new_instance.launch')
|
|
}}</Button>
|
|
</template>
|
|
</template>
|
|
<template v-else>
|
|
<Button @click="handleCreateNewInstance">{{ t('dialog.new_instance.create_instance') }}</Button>
|
|
</template>
|
|
</DialogFooter>
|
|
<DialogFooter v-else-if="newInstanceDialog.selectedTab === 'Legacy'">
|
|
<Button variant="outline" class="mr-2" @click="copyInstanceUrl(newInstanceDialog.location)">{{
|
|
t('dialog.new_instance.copy_url')
|
|
}}</Button>
|
|
<Button variant="outline" class="mr-2" @click="selfInvite(newInstanceDialog.location)">{{
|
|
t('dialog.new_instance.self_invite')
|
|
}}</Button>
|
|
<Button
|
|
variant="outline"
|
|
:disabled="
|
|
(newInstanceDialog.accessType === 'friends' || newInstanceDialog.accessType === 'invite') &&
|
|
newInstanceDialog.legacyUserId !== currentUser.id
|
|
"
|
|
@click="showInviteDialog(newInstanceDialog.location)"
|
|
>{{ t('dialog.new_instance.invite') }}</Button
|
|
>
|
|
<template v-if="canOpenInstanceInGame">
|
|
<Button
|
|
variant="secondary"
|
|
class="mr-2"
|
|
@click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)"
|
|
>{{ t('dialog.new_instance.launch') }}</Button
|
|
>
|
|
<Button @click="handleAttachGame(newInstanceDialog.location, newInstanceDialog.shortName)">
|
|
{{ t('dialog.new_instance.open_ingame') }}
|
|
</Button>
|
|
</template>
|
|
<template v-else>
|
|
<Button @click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)">{{
|
|
t('dialog.new_instance.launch')
|
|
}}</Button>
|
|
</template>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
|
|
<InviteDialog :invite-dialog="inviteDialog" @closeInviteDialog="closeInviteDialog" />
|
|
</Dialog>
|
|
</template>
|
|
|
|
<script setup>
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle
|
|
} from '@/components/ui/dialog';
|
|
import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field';
|
|
import { computed, ref, toRef } from 'vue';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Check as CheckIcon } from 'lucide-vue-next';
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
|
import { InputGroupField } from '@/components/ui/input-group';
|
|
import { TabsUnderline } from '@/components/ui/tabs';
|
|
import { storeToRefs } from 'pinia';
|
|
import { toast } from 'vue-sonner';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
import {
|
|
copyToClipboard,
|
|
getLaunchURL,
|
|
hasGroupPermission,
|
|
isRealInstance,
|
|
parseLocation
|
|
} from '../../../shared/utils';
|
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '../../ui/select';
|
|
import {
|
|
useFriendStore,
|
|
useGroupStore,
|
|
useInviteStore,
|
|
useLaunchStore,
|
|
useLocationStore,
|
|
useUserStore
|
|
} from '../../../stores';
|
|
import { ToggleGroup, ToggleGroupItem } from '../../ui/toggle-group';
|
|
import { instanceRequest, queryRequest } from '../../../api';
|
|
import { VirtualCombobox } from '../../ui/virtual-combobox';
|
|
import { useNewInstanceBuilder } from './useNewInstanceBuilder';
|
|
|
|
import InviteDialog from '../InviteDialog/InviteDialog.vue';
|
|
import { useUserDisplay } from '../../../composables/useUserDisplay';
|
|
|
|
const { userImage, userStatusClass } = useUserDisplay();
|
|
|
|
const props = defineProps({
|
|
newInstanceDialogLocationTag: {
|
|
type: String,
|
|
required: true
|
|
}
|
|
});
|
|
const newInstanceTabs = computed(() => [
|
|
{ value: 'Normal', label: t('dialog.new_instance.normal') },
|
|
{ value: 'Legacy', label: t('dialog.new_instance.legacy') }
|
|
]);
|
|
|
|
const { t } = useI18n();
|
|
|
|
const { friends, vipFriends, onlineFriends, activeFriends, offlineFriends } = storeToRefs(useFriendStore());
|
|
const { currentUserGroups } = storeToRefs(useGroupStore());
|
|
const { lastLocation } = storeToRefs(useLocationStore());
|
|
const { showLaunchDialog, tryOpenInstanceInVrc } = useLaunchStore();
|
|
const { currentUser } = storeToRefs(useUserStore());
|
|
const { canOpenInstanceInGame } = useInviteStore();
|
|
|
|
const {
|
|
newInstanceDialog,
|
|
isLocalUserVrcPlusSupporter,
|
|
buildInstance,
|
|
buildLegacyInstance,
|
|
updateNewInstanceDialog,
|
|
handleCreateNewInstance,
|
|
newInstanceTabClick,
|
|
handleRoleIdsChange
|
|
} = useNewInstanceBuilder(toRef(props, 'newInstanceDialogLocationTag'));
|
|
|
|
const inviteDialog = ref({
|
|
visible: false,
|
|
loading: false,
|
|
worldId: '',
|
|
worldName: '',
|
|
userIds: [],
|
|
friendsInInstance: []
|
|
});
|
|
|
|
const instanceCreatableGroups = computed(() => {
|
|
return Array.from(currentUserGroups.value.values()).filter((group) => {
|
|
if (!group) {
|
|
return false;
|
|
}
|
|
return (
|
|
hasGroupPermission(group, 'group-instance-public-create') ||
|
|
hasGroupPermission(group, 'group-instance-plus-create') ||
|
|
hasGroupPermission(group, 'group-instance-open-create')
|
|
);
|
|
});
|
|
});
|
|
|
|
const normalGroupPickerGroups = computed(() => [
|
|
{
|
|
key: 'instanceCreatableGroups',
|
|
label: t('dialog.new_instance.group_placeholder'),
|
|
items: instanceCreatableGroups.value.map((group) => ({
|
|
value: String(group.id),
|
|
label: group.name,
|
|
search: group.name,
|
|
iconUrl: group.iconUrl
|
|
}))
|
|
}
|
|
]);
|
|
|
|
const legacyGroupPickerGroups = computed(() => [
|
|
{
|
|
key: 'currentUserGroups',
|
|
label: t('dialog.new_instance.group_placeholder'),
|
|
items: Array.from(currentUserGroups.value.values())
|
|
.filter(Boolean)
|
|
.map((group) => ({
|
|
value: String(group.id),
|
|
label: group.name,
|
|
search: group.name,
|
|
iconUrl: group.iconUrl
|
|
}))
|
|
}
|
|
]);
|
|
|
|
const selectedRoleSummary = computed(() => {
|
|
const roleIds = newInstanceDialog.value.roleIds ?? [];
|
|
if (!Array.isArray(roleIds) || roleIds.length === 0) {
|
|
return '';
|
|
}
|
|
const roleById = new Map((newInstanceDialog.value.selectedGroupRoles ?? []).map((r) => [r.id, r.name]));
|
|
const names = roleIds.map((id) => roleById.get(id) ?? String(id));
|
|
return names.slice(0, 3).join(', ') + (names.length > 3 ? ` +${names.length - 3}` : '');
|
|
});
|
|
|
|
const friendSections = computed(() => [
|
|
{
|
|
key: 'vip',
|
|
label: t('side_panel.favorite'),
|
|
friends: vipFriends.value
|
|
},
|
|
{
|
|
key: 'online',
|
|
label: t('side_panel.online'),
|
|
friends: onlineFriends.value
|
|
},
|
|
{
|
|
key: 'active',
|
|
label: t('side_panel.active'),
|
|
friends: activeFriends.value
|
|
},
|
|
{
|
|
key: 'offline',
|
|
label: t('side_panel.offline'),
|
|
friends: offlineFriends.value
|
|
}
|
|
]);
|
|
|
|
const friendById = computed(() => {
|
|
const map = new Map();
|
|
for (const section of friendSections.value) {
|
|
for (const friend of section.friends ?? []) {
|
|
map.set(friend.id, friend);
|
|
}
|
|
}
|
|
return map;
|
|
});
|
|
|
|
/**
|
|
*
|
|
* @param userId
|
|
*/
|
|
function resolveUserDisplayName(userId) {
|
|
if (currentUser.value?.id && currentUser.value.id === userId) {
|
|
return currentUser.value.displayName;
|
|
}
|
|
const friend = friendById.value.get(userId);
|
|
return friend?.ref?.displayName ?? friend?.name ?? String(userId);
|
|
}
|
|
|
|
const creatorPickerGroups = computed(() => {
|
|
const groups = [];
|
|
|
|
if (currentUser.value) {
|
|
groups.push({
|
|
key: 'me',
|
|
label: t('side_panel.me'),
|
|
items: [
|
|
{
|
|
value: String(currentUser.value.id),
|
|
label: currentUser.value.displayName,
|
|
search: currentUser.value.displayName,
|
|
user: currentUser.value
|
|
}
|
|
]
|
|
});
|
|
}
|
|
|
|
const addFriendGroup = ({ key, label, friends }) => {
|
|
if (!friends?.length) return;
|
|
groups.push({
|
|
key,
|
|
label,
|
|
items: friends.map((friend) => {
|
|
const user = friend?.ref ?? null;
|
|
const displayName = resolveUserDisplayName(friend.id);
|
|
return {
|
|
value: String(friend.id),
|
|
label: displayName,
|
|
search: displayName,
|
|
user
|
|
};
|
|
})
|
|
});
|
|
};
|
|
|
|
friendSections.value.forEach(addFriendGroup);
|
|
|
|
return groups;
|
|
});
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function closeInviteDialog() {
|
|
inviteDialog.value.visible = false;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param tag
|
|
*/
|
|
function showInviteDialog(tag) {
|
|
if (!isRealInstance(tag)) {
|
|
return;
|
|
}
|
|
const L = parseLocation(tag);
|
|
queryRequest
|
|
.fetch('world', {
|
|
worldId: L.worldId
|
|
})
|
|
.then((args) => {
|
|
const D = inviteDialog.value;
|
|
D.userIds = [];
|
|
D.worldId = L.tag;
|
|
D.worldName = args.ref.name;
|
|
D.friendsInInstance = [];
|
|
const friendsInCurrentInstance = lastLocation.value.friendList;
|
|
for (const friend of friendsInCurrentInstance.values()) {
|
|
const ctx = friends.value.get(friend.userId);
|
|
if (typeof ctx?.ref === 'undefined') {
|
|
continue;
|
|
}
|
|
D.friendsInInstance.push(ctx);
|
|
}
|
|
D.visible = true;
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param location
|
|
* @param shortName
|
|
*/
|
|
function handleAttachGame(location, shortName) {
|
|
tryOpenInstanceInVrc(location, shortName);
|
|
closeInviteDialog();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param location
|
|
*/
|
|
function selfInvite(location) {
|
|
const L = parseLocation(location);
|
|
if (!L.isRealInstance) {
|
|
return;
|
|
}
|
|
instanceRequest
|
|
.selfInvite({
|
|
instanceId: L.instanceId,
|
|
worldId: L.worldId
|
|
})
|
|
.then((args) => {
|
|
toast.success(t('message.invite.self_sent'));
|
|
return args;
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param location
|
|
*/
|
|
async function copyInstanceUrl(location) {
|
|
const L = parseLocation(location);
|
|
const args = await instanceRequest.getInstanceShortName({
|
|
worldId: L.worldId,
|
|
instanceId: L.instanceId
|
|
});
|
|
if (args.json) {
|
|
if (args.json.shortName) {
|
|
L.shortName = args.json.shortName;
|
|
}
|
|
const resLocation = `${args.instance.worldId}:${args.instance.instanceId}`;
|
|
if (resLocation === newInstanceDialog.value.location) {
|
|
const shortName = args.json.shortName;
|
|
const secureOrShortName = args.json.shortName || args.json.secureName;
|
|
newInstanceDialog.value.shortName = shortName;
|
|
newInstanceDialog.value.secureOrShortName = secureOrShortName;
|
|
updateNewInstanceDialog(true);
|
|
}
|
|
}
|
|
const newUrl = getLaunchURL(L);
|
|
copyToClipboard(newUrl);
|
|
}
|
|
</script>
|