mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-29 19:53:47 +02:00
replace element plus components
This commit is contained in:
@@ -5,81 +5,83 @@
|
||||
<DialogTitle>{{ t('dialog.favorite.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div v-loading="loading">
|
||||
<span style="display: block; text-align: center">{{ t('dialog.favorite.vrchat_favorites') }}</span>
|
||||
<template v-if="favoriteDialog.currentGroup && favoriteDialog.currentGroup.key">
|
||||
<Button
|
||||
variant="outline"
|
||||
style="width: 100%; white-space: initial"
|
||||
class="my-1"
|
||||
@click="deleteFavoriteNoConfirm(favoriteDialog.objectId)">
|
||||
<Check />{{ favoriteDialog.currentGroup.displayName }} ({{ favoriteDialog.currentGroup.count }} /
|
||||
{{ favoriteDialog.currentGroup.capacity }})
|
||||
</Button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Button
|
||||
variant="outline"
|
||||
v-for="group in groups"
|
||||
:key="group.key"
|
||||
style="width: 100%; white-space: initial"
|
||||
class="my-1"
|
||||
@click="addFavorite(group)">
|
||||
{{ group.displayName }} ({{ group.count }} / {{ group.capacity }})
|
||||
</Button>
|
||||
</template>
|
||||
<span style="display: block; text-align: center">{{ t('dialog.favorite.vrchat_favorites') }}</span>
|
||||
<template v-if="favoriteDialog.currentGroup && favoriteDialog.currentGroup.key">
|
||||
<Button
|
||||
variant="outline"
|
||||
style="width: 100%; white-space: initial"
|
||||
class="my-1"
|
||||
@click="deleteFavoriteNoConfirm(favoriteDialog.objectId)">
|
||||
<Check />{{ favoriteDialog.currentGroup.displayName }} ({{
|
||||
favoriteDialog.currentGroup.count
|
||||
}}
|
||||
/ {{ favoriteDialog.currentGroup.capacity }})
|
||||
</Button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Button
|
||||
variant="outline"
|
||||
v-for="group in groups"
|
||||
:key="group.key"
|
||||
style="width: 100%; white-space: initial"
|
||||
class="my-1"
|
||||
@click="addFavorite(group)">
|
||||
{{ group.displayName }} ({{ group.count }} / {{ group.capacity }})
|
||||
</Button>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="favoriteDialog.type === 'world'" style="margin-top: 20px">
|
||||
<span style="display: block; text-align: center">{{ t('dialog.favorite.local_favorites') }}</span>
|
||||
<template v-for="group in localWorldFavoriteGroups" :key="group">
|
||||
<Button
|
||||
variant="outline"
|
||||
v-if="hasLocalWorldFavorite(favoriteDialog.objectId, group)"
|
||||
style="width: 100%; white-space: initial"
|
||||
class="my-1"
|
||||
@click="removeLocalWorldFavorite(favoriteDialog.objectId, group)">
|
||||
<Check />{{ group }} ({{ localWorldFavGroupLength(group) }})
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
v-else
|
||||
style="width: 100%; white-space: initial"
|
||||
class="my-1"
|
||||
@click="addLocalWorldFavorite(favoriteDialog.objectId, group)">
|
||||
{{ group }} ({{ localWorldFavGroupLength(group) }})
|
||||
</Button>
|
||||
</template>
|
||||
<span style="display: block; text-align: center">{{ t('dialog.favorite.local_favorites') }}</span>
|
||||
<template v-for="group in localWorldFavoriteGroups" :key="group">
|
||||
<Button
|
||||
variant="outline"
|
||||
v-if="hasLocalWorldFavorite(favoriteDialog.objectId, group)"
|
||||
style="width: 100%; white-space: initial"
|
||||
class="my-1"
|
||||
@click="removeLocalWorldFavorite(favoriteDialog.objectId, group)">
|
||||
<Check />{{ group }} ({{ localWorldFavGroupLength(group) }})
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
v-else
|
||||
style="width: 100%; white-space: initial"
|
||||
class="my-1"
|
||||
@click="addLocalWorldFavorite(favoriteDialog.objectId, group)">
|
||||
{{ group }} ({{ localWorldFavGroupLength(group) }})
|
||||
</Button>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="favoriteDialog.type === 'avatar'" style="margin-top: 20px">
|
||||
<span style="text-align: center">{{ t('dialog.favorite.local_avatar_favorites') }}</span>
|
||||
<template v-for="group in localAvatarFavoriteGroups" :key="group">
|
||||
<Button
|
||||
variant="outline"
|
||||
v-if="hasLocalAvatarFavorite(favoriteDialog.objectId, group)"
|
||||
style="width: 100%; white-space: initial"
|
||||
class="my-1"
|
||||
@click="removeLocalAvatarFavorite(favoriteDialog.objectId, group)">
|
||||
<Check />{{ group }} ({{ localAvatarFavGroupLength(group) }})
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
v-else
|
||||
style="width: 100%; white-space: initial"
|
||||
class="my-1"
|
||||
:disabled="!isLocalUserVrcPlusSupporter"
|
||||
@click="addLocalAvatarFavorite(favoriteDialog.objectId, group)">
|
||||
{{ group }} ({{ localAvatarFavGroupLength(group) }})
|
||||
</Button>
|
||||
</template>
|
||||
<span style="text-align: center">{{ t('dialog.favorite.local_avatar_favorites') }}</span>
|
||||
<template v-for="group in localAvatarFavoriteGroups" :key="group">
|
||||
<Button
|
||||
variant="outline"
|
||||
v-if="hasLocalAvatarFavorite(favoriteDialog.objectId, group)"
|
||||
style="width: 100%; white-space: initial"
|
||||
class="my-1"
|
||||
@click="removeLocalAvatarFavorite(favoriteDialog.objectId, group)">
|
||||
<Check />{{ group }} ({{ localAvatarFavGroupLength(group) }})
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
v-else
|
||||
style="width: 100%; white-space: initial"
|
||||
class="my-1"
|
||||
:disabled="!isLocalUserVrcPlusSupporter"
|
||||
@click="addLocalAvatarFavorite(favoriteDialog.objectId, group)">
|
||||
{{ group }} ({{ localAvatarFavGroupLength(group) }})
|
||||
</Button>
|
||||
</template>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Check } from 'lucide-vue-next';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
|
||||
@@ -4,51 +4,20 @@
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('nav_menu.custom_nav.dialog_title') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="custom-nav-dialog__list" v-if="localLayout.length">
|
||||
<div
|
||||
v-for="(entry, index) in localLayout"
|
||||
:key="entry.key || entry.id"
|
||||
:class="['custom-nav-entry', `custom-nav-entry--${entry.type}`]">
|
||||
<template v-if="entry.type === 'item'">
|
||||
<div class="custom-nav-entry__info">
|
||||
<i :class="definitionsMap.get(entry.key)?.icon"></i>
|
||||
<span>{{ t(definitionsMap.get(entry.key)?.labelKey || entry.key) }}</span>
|
||||
</div>
|
||||
<div class="custom-nav-entry__controls">
|
||||
<div class="custom-nav-entry__move">
|
||||
<Button
|
||||
class="rounded-full w-6 h-6 text-xs"
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
:disabled="index === 0"
|
||||
@click="handleMoveEntry(index, -1)">
|
||||
<i class="ri-arrow-up-line"></i>
|
||||
</Button>
|
||||
<Button
|
||||
class="rounded-full w-6 h-6 text-xs"
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
:disabled="index === localLayout.length - 1"
|
||||
@click="handleMoveEntry(index, 1)">
|
||||
<i class="ri-arrow-down-line"></i>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="custom-nav-entry__folder-header">
|
||||
<div class="custom-nav-dialog__list" v-if="localLayout.length">
|
||||
<div
|
||||
v-for="(entry, index) in localLayout"
|
||||
:key="entry.key || entry.id"
|
||||
:class="['custom-nav-entry', `custom-nav-entry--${entry.type}`]">
|
||||
<template v-if="entry.type === 'item'">
|
||||
<div class="custom-nav-entry__info">
|
||||
<i :class="entry.icon || defaultFolderIcon"></i>
|
||||
<span>{{ entry.name?.trim() || t('nav_menu.custom_nav.folder_name_placeholder') }}</span>
|
||||
<i :class="definitionsMap.get(entry.key)?.icon"></i>
|
||||
<span>{{ t(definitionsMap.get(entry.key)?.labelKey || entry.key) }}</span>
|
||||
</div>
|
||||
<div class="custom-nav-entry__actions">
|
||||
<Button size="icon-sm w-6 h-6 text-xs" variant="outline" @click="openFolderEditor(index)">
|
||||
<i class="ri-edit-box-line"></i>
|
||||
{{ t('nav_menu.custom_nav.edit_folder') }}
|
||||
</Button>
|
||||
<div class="custom-nav-entry__controls">
|
||||
<div class="custom-nav-entry__move">
|
||||
<Button
|
||||
class="rounded-full text-xs w-6 h-6"
|
||||
class="rounded-full w-6 h-6 text-xs"
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
:disabled="index === 0"
|
||||
@@ -56,7 +25,7 @@
|
||||
<i class="ri-arrow-up-line"></i>
|
||||
</Button>
|
||||
<Button
|
||||
class="rounded-full text-xs w-6 h-6"
|
||||
class="rounded-full w-6 h-6 text-xs"
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
:disabled="index === localLayout.length - 1"
|
||||
@@ -65,54 +34,90 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="custom-nav-entry__folder-items">
|
||||
<template v-if="entry.items?.length">
|
||||
<Badge
|
||||
v-for="key in entry.items"
|
||||
:key="`${entry.id}-${key}`"
|
||||
variant="outline"
|
||||
class="custom-nav-entry__folder-tag">
|
||||
{{ t(definitionsMap.get(key)?.labelKey || key) }}
|
||||
</Badge>
|
||||
</template>
|
||||
<span v-else class="custom-nav-entry__folder-empty">
|
||||
{{ t('nav_menu.custom_nav.folder_empty') }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="custom-nav-entry__folder-header">
|
||||
<div class="custom-nav-entry__info">
|
||||
<i :class="entry.icon || defaultFolderIcon"></i>
|
||||
<span>{{
|
||||
entry.name?.trim() || t('nav_menu.custom_nav.folder_name_placeholder')
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="custom-nav-entry__actions">
|
||||
<Button
|
||||
size="icon-sm w-6 h-6 text-xs"
|
||||
variant="outline"
|
||||
@click="openFolderEditor(index)">
|
||||
<i class="ri-edit-box-line"></i>
|
||||
{{ t('nav_menu.custom_nav.edit_folder') }}
|
||||
</Button>
|
||||
<div class="custom-nav-entry__move">
|
||||
<Button
|
||||
class="rounded-full text-xs w-6 h-6"
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
:disabled="index === 0"
|
||||
@click="handleMoveEntry(index, -1)">
|
||||
<i class="ri-arrow-up-line"></i>
|
||||
</Button>
|
||||
<Button
|
||||
class="rounded-full text-xs w-6 h-6"
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
:disabled="index === localLayout.length - 1"
|
||||
@click="handleMoveEntry(index, 1)">
|
||||
<i class="ri-arrow-down-line"></i>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="custom-nav-entry__folder-items">
|
||||
<template v-if="entry.items?.length">
|
||||
<Badge
|
||||
v-for="key in entry.items"
|
||||
:key="`${entry.id}-${key}`"
|
||||
variant="outline"
|
||||
class="custom-nav-entry__folder-tag">
|
||||
{{ t(definitionsMap.get(key)?.labelKey || key) }}
|
||||
</Badge>
|
||||
</template>
|
||||
<span v-else class="custom-nav-entry__folder-empty">
|
||||
{{ t('nav_menu.custom_nav.folder_empty') }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-alert
|
||||
v-if="invalidFolders.length"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
:title="t('nav_menu.custom_nav.invalid_folder')" />
|
||||
<!-- <el-alert
|
||||
v-if="invalidFolders.length"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
:title="t('nav_menu.custom_nav.invalid_folder')" /> -->
|
||||
<DialogFooter>
|
||||
<div class="custom-nav-dialog__footer">
|
||||
<div class="custom-nav-dialog__footer-left">
|
||||
<Button variant="outline" @click="openFolderEditor()">
|
||||
{{ t('nav_menu.custom_nav.add_folder') }}
|
||||
</Button>
|
||||
<Button variant="outline" @click="handleReset">
|
||||
{{ t('nav_menu.custom_nav.restore_default') }}
|
||||
</Button>
|
||||
<div class="custom-nav-dialog__footer">
|
||||
<div class="custom-nav-dialog__footer-left">
|
||||
<Button variant="outline" @click="openFolderEditor()">
|
||||
{{ t('nav_menu.custom_nav.add_folder') }}
|
||||
</Button>
|
||||
<Button variant="outline" @click="handleReset">
|
||||
{{ t('nav_menu.custom_nav.restore_default') }}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="custom-nav-dialog__footer-right">
|
||||
<Button variant="secondary" @click="handleClose">
|
||||
{{ t('nav_menu.custom_nav.cancel') }}
|
||||
</Button>
|
||||
<Button :disabled="isSaveDisabled" @click="handleSave">
|
||||
{{ t('nav_menu.custom_nav.save') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="custom-nav-dialog__footer-right">
|
||||
<Button variant="secondary" @click="handleClose">
|
||||
{{ t('nav_menu.custom_nav.cancel') }}
|
||||
</Button>
|
||||
<Button :disabled="isSaveDisabled" @click="handleSave">
|
||||
{{ t('nav_menu.custom_nav.save') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:open="folderEditor.visible">
|
||||
<DialogContent class="folder-editor-dialog">
|
||||
<DialogContent class="folder-editor-dialog sm:max-w-[50vw]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{{
|
||||
@@ -122,113 +127,151 @@
|
||||
}}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="folder-editor">
|
||||
<div class="folder-editor__form">
|
||||
<InputGroupField
|
||||
v-model="folderEditor.data.name"
|
||||
:placeholder="t('nav_menu.custom_nav.folder_name_placeholder')" />
|
||||
<IconPicker v-model="folderEditor.data.icon" class="folder-editor__icon-picker" />
|
||||
</div>
|
||||
<div class="folder-editor__lists">
|
||||
<div class="folder-editor__column">
|
||||
<div class="folder-editor__column-title">
|
||||
{{ t('nav_menu.custom_nav.folder_available') }}
|
||||
</div>
|
||||
<div v-if="!folderEditorAvailableItems.length" class="folder-editor__empty">
|
||||
{{ t('nav_menu.custom_nav.folder_empty') }}
|
||||
</div>
|
||||
<el-scrollbar v-else always class="folder-editor__scroll">
|
||||
<div v-for="item in folderEditorAvailableItems" :key="item.key" class="folder-editor__option">
|
||||
<label class="folder-editor__option-label">
|
||||
<Checkbox
|
||||
:model-value="folderEditor.data.items.includes(item.key)"
|
||||
@update:modelValue="(val) => toggleFolderItem(item.key, val)" />
|
||||
<span>
|
||||
<i :class="item.icon"></i>
|
||||
{{ t(item.labelKey) }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<div class="folder-editor">
|
||||
<div class="folder-editor__form">
|
||||
<InputGroupField
|
||||
class="col-span-2"
|
||||
v-model="folderEditor.data.name"
|
||||
:placeholder="t('nav_menu.custom_nav.folder_name_placeholder')" />
|
||||
<InputGroupField
|
||||
class="col-span-2"
|
||||
v-model="folderEditor.data.icon"
|
||||
:placeholder="t('nav_menu.custom_nav.folder_icon_placeholder')">
|
||||
<template #trailing>
|
||||
<HoverCard>
|
||||
<HoverCardTrigger as-child>
|
||||
<InputGroupButton
|
||||
size="icon-xs"
|
||||
:aria-label="t('nav_menu.custom_nav.folder_icon_placeholder')">
|
||||
<LinkIcon class="size-3.5" />
|
||||
</InputGroupButton>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent side="bottom" align="end" class="w-80">
|
||||
<div class="text-sm leading-snug">
|
||||
<div>
|
||||
Find the icon you want on this site and paste its class name here, e.g.
|
||||
<span class="font-mono">ri-arrow-left-up-line</span>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<a
|
||||
class="x-link"
|
||||
@click.prevent="openExternalLink('https://remixicon.com/')">
|
||||
https://remixicon.com/
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
</template>
|
||||
</InputGroupField>
|
||||
</div>
|
||||
<div class="folder-editor__column folder-editor__column--selected">
|
||||
<div class="folder-editor__column-title">
|
||||
{{ t('nav_menu.custom_nav.folder_selected') }}
|
||||
</div>
|
||||
<div v-if="!folderEditor.data.items.length" class="folder-editor__empty">
|
||||
{{ t('nav_menu.custom_nav.folder_selected_empty') }}
|
||||
</div>
|
||||
<div
|
||||
v-for="(key, index) in folderEditor.data.items"
|
||||
:key="`selected-${key}`"
|
||||
class="folder-editor__selected-item">
|
||||
<div class="folder-editor__selected-label">
|
||||
<i :class="definitionsMap.get(key)?.icon"></i>
|
||||
<span>{{ t(definitionsMap.get(key)?.labelKey || key) }}</span>
|
||||
<div class="folder-editor__lists">
|
||||
<div class="folder-editor__column">
|
||||
<div class="folder-editor__column-title">
|
||||
{{ t('nav_menu.custom_nav.folder_available') }}
|
||||
</div>
|
||||
<div class="folder-editor__selected-actions">
|
||||
<div class="custom-nav-entry__move">
|
||||
<Button
|
||||
class="rounded-full text-xs w-6 h-6"
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
:disabled="index === 0"
|
||||
@click="handleFolderItemMove(index, -1)">
|
||||
<i class="ri-arrow-up-line"></i>
|
||||
</Button>
|
||||
<Button
|
||||
class="rounded-full text-xs w-6 h-6"
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
:disabled="index === folderEditor.data.items.length - 1"
|
||||
@click="handleFolderItemMove(index, 1)">
|
||||
<i class="ri-arrow-down-line"></i>
|
||||
<div v-if="!folderEditorAvailableItems.length" class="folder-editor__empty">
|
||||
{{ t('nav_menu.custom_nav.folder_empty') }}
|
||||
</div>
|
||||
<ScrollArea v-else type="always" class="folder-editor__scroll">
|
||||
<div
|
||||
v-for="item in folderEditorAvailableItems"
|
||||
:key="item.key"
|
||||
class="folder-editor__option">
|
||||
<label class="folder-editor__option-label">
|
||||
<Checkbox
|
||||
:model-value="folderEditor.data.items.includes(item.key)"
|
||||
@update:modelValue="(val) => toggleFolderItem(item.key, val)" />
|
||||
<span>
|
||||
<i :class="item.icon"></i>
|
||||
{{ t(item.labelKey) }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
<div class="folder-editor__column folder-editor__column--selected">
|
||||
<div class="folder-editor__column-title">
|
||||
{{ t('nav_menu.custom_nav.folder_selected') }}
|
||||
</div>
|
||||
<div v-if="!folderEditor.data.items.length" class="folder-editor__empty">
|
||||
{{ t('nav_menu.custom_nav.folder_selected_empty') }}
|
||||
</div>
|
||||
<div
|
||||
v-for="(key, index) in folderEditor.data.items"
|
||||
:key="`selected-${key}`"
|
||||
class="folder-editor__selected-item">
|
||||
<div class="folder-editor__selected-label">
|
||||
<i :class="definitionsMap.get(key)?.icon"></i>
|
||||
<span>{{ t(definitionsMap.get(key)?.labelKey || key) }}</span>
|
||||
</div>
|
||||
<div class="folder-editor__selected-actions">
|
||||
<div class="custom-nav-entry__move">
|
||||
<Button
|
||||
class="rounded-full text-xs w-6 h-6"
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
:disabled="index === 0"
|
||||
@click="handleFolderItemMove(index, -1)">
|
||||
<i class="ri-arrow-up-line"></i>
|
||||
</Button>
|
||||
<Button
|
||||
class="rounded-full text-xs w-6 h-6"
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
:disabled="index === folderEditor.data.items.length - 1"
|
||||
@click="handleFolderItemMove(index, 1)">
|
||||
<i class="ri-arrow-down-line"></i>
|
||||
</Button>
|
||||
</div>
|
||||
<Button size="sm" variant="outline" @click="toggleFolderItem(key, false)">
|
||||
{{ t('nav_menu.custom_nav.remove_from_folder') }}
|
||||
</Button>
|
||||
</div>
|
||||
<Button size="sm" variant="outline" @click="toggleFolderItem(key, false)">
|
||||
{{ t('nav_menu.custom_nav.remove_from_folder') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<div class="folder-editor__footer">
|
||||
<Button
|
||||
variant="destructive"
|
||||
v-if="folderEditor.isEditing"
|
||||
:disabled="!canDeleteFolder"
|
||||
@click="handleFolderEditorDelete">
|
||||
{{ t('nav_menu.custom_nav.delete_folder') }}
|
||||
</Button>
|
||||
<div class="folder-editor__footer-spacer"></div>
|
||||
<Button variant="secondary" class="mr-2" @click="closeFolderEditor">
|
||||
{{ t('nav_menu.custom_nav.cancel') }}
|
||||
</Button>
|
||||
<Button :disabled="folderEditorSaveDisabled" @click="handleFolderEditorSave">
|
||||
{{ t('nav_menu.custom_nav.save') }}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="folder-editor__footer">
|
||||
<Button
|
||||
variant="destructive"
|
||||
v-if="folderEditor.isEditing"
|
||||
:disabled="!canDeleteFolder"
|
||||
@click="handleFolderEditorDelete">
|
||||
{{ t('nav_menu.custom_nav.delete_folder') }}
|
||||
</Button>
|
||||
<div class="folder-editor__footer-spacer"></div>
|
||||
<Button variant="secondary" class="mr-2" @click="closeFolderEditor">
|
||||
{{ t('nav_menu.custom_nav.cancel') }}
|
||||
</Button>
|
||||
<Button :disabled="folderEditorSaveDisabled" @click="handleFolderEditorSave">
|
||||
{{ t('nav_menu.custom_nav.save') }}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Link as LinkIcon } from 'lucide-vue-next';
|
||||
import { openExternalLink } from '@/shared/utils/common';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { InputGroupButton, InputGroupField } from '../ui/input-group';
|
||||
import { Badge } from '../ui/badge';
|
||||
import { Checkbox } from '../ui/checkbox';
|
||||
import { InputGroupField } from '../ui/input-group';
|
||||
import { ScrollArea } from '../ui/scroll-area';
|
||||
import { navDefinitions } from '../../shared/constants/ui.js';
|
||||
|
||||
import IconPicker from '../IconPicker.vue';
|
||||
// import IconPicker from '../IconPicker.vue';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -240,8 +283,7 @@
|
||||
default: () => []
|
||||
},
|
||||
defaultFolderIcon: {
|
||||
type: String,
|
||||
default: 'ri-menu-fold-line'
|
||||
type: String
|
||||
}
|
||||
});
|
||||
|
||||
@@ -258,7 +300,7 @@
|
||||
type: 'folder',
|
||||
id: entry.id,
|
||||
name: entry.name,
|
||||
icon: entry.icon || props.defaultFolderIcon,
|
||||
icon: entry.icon,
|
||||
items: Array.isArray(entry.items) ? [...entry.items] : []
|
||||
};
|
||||
}
|
||||
@@ -358,14 +400,14 @@
|
||||
folderEditor.data = {
|
||||
id: entry.id,
|
||||
name: entry.name,
|
||||
icon: entry.icon || props.defaultFolderIcon,
|
||||
icon: entry.icon,
|
||||
items: Array.isArray(entry.items) ? [...entry.items] : []
|
||||
};
|
||||
} else {
|
||||
folderEditor.data = {
|
||||
id: `custom-folder-${dayjs().toISOString()}-${Math.random().toString().slice(2, 7)}`,
|
||||
name: '',
|
||||
icon: props.defaultFolderIcon,
|
||||
icon: '',
|
||||
items: []
|
||||
};
|
||||
}
|
||||
@@ -399,6 +441,7 @@
|
||||
|
||||
const applyFolderChanges = () => {
|
||||
const sanitizedItems = folderEditor.data.items.filter((key) => definitionsMap.value.has(key));
|
||||
const sanitizedIcon = folderEditor.data.icon?.trim() || '';
|
||||
const entries = [...localLayout.value];
|
||||
|
||||
if (folderEditor.isEditing) {
|
||||
@@ -421,7 +464,7 @@
|
||||
type: 'folder',
|
||||
id: folderEditor.data.id,
|
||||
name: folderEditor.data.name.trim(),
|
||||
icon: folderEditor.data.icon || props.defaultFolderIcon,
|
||||
icon: sanitizedIcon,
|
||||
items: sanitizedItems
|
||||
});
|
||||
|
||||
@@ -442,7 +485,7 @@
|
||||
type: 'folder',
|
||||
id: folderEditor.data.id,
|
||||
name: folderEditor.data.name.trim(),
|
||||
icon: folderEditor.data.icon || props.defaultFolderIcon,
|
||||
icon: sanitizedIcon,
|
||||
items: sanitizedItems
|
||||
});
|
||||
|
||||
|
||||
@@ -6,64 +6,64 @@
|
||||
</DialogHeader>
|
||||
|
||||
<div v-if="inviteGroupDialog.visible" v-loading="inviteGroupDialog.loading">
|
||||
<span>{{ t('dialog.invite_to_group.description') }}</span>
|
||||
<br />
|
||||
<span>{{ t('dialog.invite_to_group.description') }}</span>
|
||||
<br />
|
||||
|
||||
<div style="margin-top: 15px; width: 100%">
|
||||
<VirtualCombobox
|
||||
v-model="inviteGroupDialog.groupId"
|
||||
:groups="groupPickerGroups"
|
||||
:disabled="inviteGroupDialog.loading"
|
||||
:placeholder="t('dialog.invite_to_group.choose_group_placeholder')"
|
||||
:search-placeholder="t('dialog.invite_to_group.choose_group_placeholder')"
|
||||
:clearable="true"
|
||||
:close-on-select="true"
|
||||
:deselect-on-reselect="true">
|
||||
<template #item="{ item, selected }">
|
||||
<div class="x-friend-item flex w-full items-center">
|
||||
<div class="avatar">
|
||||
<img :src="item.iconUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="item.label"></span>
|
||||
</div>
|
||||
<CheckIcon :class="['ml-auto size-4', selected ? 'opacity-100' : 'opacity-0']" />
|
||||
</div>
|
||||
</template>
|
||||
</VirtualCombobox>
|
||||
</div>
|
||||
|
||||
<div style="width: 100%; margin-top: 15px">
|
||||
<VirtualCombobox
|
||||
v-model="inviteGroupDialog.userIds"
|
||||
:groups="friendPickerGroups"
|
||||
multiple
|
||||
:disabled="inviteGroupDialog.loading"
|
||||
:placeholder="t('dialog.invite_to_group.choose_friends_placeholder')"
|
||||
:search-placeholder="t('dialog.invite_to_group.choose_friends_placeholder')"
|
||||
:clearable="true">
|
||||
<template #item="{ item, selected }">
|
||||
<div class="x-friend-item flex w-full items-center">
|
||||
<template v-if="item.user">
|
||||
<div class="avatar" :class="userStatusClass(item.user)">
|
||||
<img :src="userImage(item.user)" loading="lazy" />
|
||||
<div style="margin-top: 15px; width: 100%">
|
||||
<VirtualCombobox
|
||||
v-model="inviteGroupDialog.groupId"
|
||||
:groups="groupPickerGroups"
|
||||
:disabled="inviteGroupDialog.loading"
|
||||
:placeholder="t('dialog.invite_to_group.choose_group_placeholder')"
|
||||
:search-placeholder="t('dialog.invite_to_group.choose_group_placeholder')"
|
||||
:clearable="true"
|
||||
:close-on-select="true"
|
||||
:deselect-on-reselect="true">
|
||||
<template #item="{ item, selected }">
|
||||
<div class="x-friend-item flex w-full items-center">
|
||||
<div class="avatar">
|
||||
<img :src="item.iconUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
class="name"
|
||||
:style="{ color: item.user.$userColour }"
|
||||
v-text="item.user.displayName"></span>
|
||||
<span class="name" v-text="item.label"></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>
|
||||
</div>
|
||||
|
||||
<CheckIcon :class="['ml-auto size-4', selected ? 'opacity-100' : 'opacity-0']" />
|
||||
</div>
|
||||
</template>
|
||||
</VirtualCombobox>
|
||||
</div>
|
||||
<div style="width: 100%; margin-top: 15px">
|
||||
<VirtualCombobox
|
||||
v-model="inviteGroupDialog.userIds"
|
||||
:groups="friendPickerGroups"
|
||||
multiple
|
||||
:disabled="inviteGroupDialog.loading"
|
||||
:placeholder="t('dialog.invite_to_group.choose_friends_placeholder')"
|
||||
:search-placeholder="t('dialog.invite_to_group.choose_friends_placeholder')"
|
||||
:clearable="true">
|
||||
<template #item="{ item, selected }">
|
||||
<div class="x-friend-item flex w-full items-center">
|
||||
<template v-if="item.user">
|
||||
<div class="avatar" :class="userStatusClass(item.user)">
|
||||
<img :src="userImage(item.user)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
class="name"
|
||||
: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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
@@ -80,9 +80,9 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Check as CheckIcon } from 'lucide-vue-next';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { toast } from 'vue-sonner';
|
||||
|
||||
@@ -5,69 +5,69 @@
|
||||
<DialogTitle>{{ t('dialog.launch.header') }}</DialogTitle>
|
||||
<DialogDescription class="sr-only">{{ t('dialog.launch.header') }}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<FieldGroup class="gap-4">
|
||||
<Field>
|
||||
<FieldLabel>{{ t('dialog.launch.url') }}</FieldLabel>
|
||||
<FieldContent class="flex-row items-center gap-2">
|
||||
<InputGroupField
|
||||
v-model="launchDialog.url"
|
||||
size="sm"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<TooltipWrapper side="right" :content="t('dialog.launch.copy_tooltip')">
|
||||
<Button
|
||||
class="rounded-full"
|
||||
size="icon-sm"
|
||||
variant="ghost"
|
||||
@click="copyInstanceMessage(launchDialog.url)"
|
||||
><Copy
|
||||
/></Button>
|
||||
</TooltipWrapper>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<Field v-if="launchDialog.shortUrl">
|
||||
<FieldLabel>
|
||||
<span class="flex items-center gap-1">
|
||||
<span>{{ t('dialog.launch.short_url') }}</span>
|
||||
<TooltipWrapper side="top" :content="t('dialog.launch.short_url_notice')">
|
||||
<AlertTriangle />
|
||||
<FieldGroup class="gap-4">
|
||||
<Field>
|
||||
<FieldLabel>{{ t('dialog.launch.url') }}</FieldLabel>
|
||||
<FieldContent class="flex-row items-center gap-2">
|
||||
<InputGroupField
|
||||
v-model="launchDialog.url"
|
||||
size="sm"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<TooltipWrapper side="right" :content="t('dialog.launch.copy_tooltip')">
|
||||
<Button
|
||||
class="rounded-full"
|
||||
size="icon-sm"
|
||||
variant="ghost"
|
||||
@click="copyInstanceMessage(launchDialog.url)"
|
||||
><Copy
|
||||
/></Button>
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
</FieldLabel>
|
||||
<FieldContent class="flex-row items-center gap-2">
|
||||
<InputGroupField
|
||||
v-model="launchDialog.shortUrl"
|
||||
size="sm"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<TooltipWrapper side="right" :content="t('dialog.launch.copy_tooltip')">
|
||||
<Button
|
||||
class="rounded-full"
|
||||
size="icon-sm"
|
||||
variant="ghost"
|
||||
@click="copyInstanceMessage(launchDialog.shortUrl)"
|
||||
><Copy
|
||||
/></Button>
|
||||
</TooltipWrapper>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel>{{ t('dialog.launch.location') }}</FieldLabel>
|
||||
<FieldContent class="flex-row items-center gap-2">
|
||||
<InputGroupField
|
||||
v-model="launchDialog.location"
|
||||
size="sm"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<TooltipWrapper side="right" :content="t('dialog.launch.copy_tooltip')">
|
||||
<Button
|
||||
class="rounded-full"
|
||||
size="icon-sm"
|
||||
variant="ghost"
|
||||
@click="copyInstanceMessage(launchDialog.location)"
|
||||
><Copy
|
||||
/></Button>
|
||||
</TooltipWrapper>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<Field v-if="launchDialog.shortUrl">
|
||||
<FieldLabel>
|
||||
<span class="flex items-center gap-1">
|
||||
<span>{{ t('dialog.launch.short_url') }}</span>
|
||||
<TooltipWrapper side="top" :content="t('dialog.launch.short_url_notice')">
|
||||
<AlertTriangle />
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
</FieldLabel>
|
||||
<FieldContent class="flex-row items-center gap-2">
|
||||
<InputGroupField
|
||||
v-model="launchDialog.shortUrl"
|
||||
size="sm"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<TooltipWrapper side="right" :content="t('dialog.launch.copy_tooltip')">
|
||||
<Button
|
||||
class="rounded-full"
|
||||
size="icon-sm"
|
||||
variant="ghost"
|
||||
@click="copyInstanceMessage(launchDialog.shortUrl)"
|
||||
><Copy
|
||||
/></Button>
|
||||
</TooltipWrapper>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel>{{ t('dialog.launch.location') }}</FieldLabel>
|
||||
<FieldContent class="flex-row items-center gap-2">
|
||||
<InputGroupField
|
||||
v-model="launchDialog.location"
|
||||
size="sm"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<TooltipWrapper side="right" :content="t('dialog.launch.copy_tooltip')">
|
||||
<Button
|
||||
class="rounded-full"
|
||||
size="icon-sm"
|
||||
variant="ghost"
|
||||
@click="copyInstanceMessage(launchDialog.location)"
|
||||
><Copy
|
||||
/></Button>
|
||||
</TooltipWrapper>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
class="mr-1.5"
|
||||
@@ -129,8 +129,14 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, ref, watch } from 'vue';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -138,6 +144,7 @@
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field';
|
||||
import { computed, onBeforeUnmount, ref, watch } from 'vue';
|
||||
import { AlertTriangle, Copy, MoreHorizontal } from 'lucide-vue-next';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ButtonGroup } from '@/components/ui/button-group';
|
||||
|
||||
@@ -6,37 +6,37 @@
|
||||
</DialogHeader>
|
||||
|
||||
<div v-if="moderateGroupDialog.visible">
|
||||
<div class="x-friend-item" style="cursor: default">
|
||||
<div class="avatar">
|
||||
<img :src="userImage(moderateGroupDialog.userObject)" loading="lazy" />
|
||||
<div class="x-friend-item" style="cursor: default">
|
||||
<div class="avatar">
|
||||
<img :src="userImage(moderateGroupDialog.userObject)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
v-if="moderateGroupDialog.userObject.id"
|
||||
class="name"
|
||||
:style="{ color: moderateGroupDialog.userObject.$userColour }"
|
||||
v-text="moderateGroupDialog.userObject.displayName"></span>
|
||||
<span v-else v-text="moderateGroupDialog.userId"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
v-if="moderateGroupDialog.userObject.id"
|
||||
class="name"
|
||||
:style="{ color: moderateGroupDialog.userObject.$userColour }"
|
||||
v-text="moderateGroupDialog.userObject.displayName"></span>
|
||||
<span v-else v-text="moderateGroupDialog.userId"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 15px; width: 100%">
|
||||
<VirtualCombobox
|
||||
:model-value="moderateGroupDialog.groupId"
|
||||
@update:modelValue="setGroupId"
|
||||
:groups="groupPickerGroups"
|
||||
:placeholder="t('dialog.moderate_group.choose_group_placeholder')"
|
||||
:search-placeholder="t('dialog.moderate_group.choose_group_placeholder')"
|
||||
:close-on-select="true">
|
||||
<template #item="{ item, selected }">
|
||||
<div class="flex w-full items-center gap-2">
|
||||
<img :src="item.iconUrl" loading="lazy" class="size-5 rounded-sm" />
|
||||
<span class="truncate text-sm" v-text="item.label"></span>
|
||||
<span v-if="selected" class="ml-auto opacity-70">✓</span>
|
||||
</div>
|
||||
</template>
|
||||
</VirtualCombobox>
|
||||
</div>
|
||||
<div style="margin-top: 15px; width: 100%">
|
||||
<VirtualCombobox
|
||||
:model-value="moderateGroupDialog.groupId"
|
||||
@update:modelValue="setGroupId"
|
||||
:groups="groupPickerGroups"
|
||||
:placeholder="t('dialog.moderate_group.choose_group_placeholder')"
|
||||
:search-placeholder="t('dialog.moderate_group.choose_group_placeholder')"
|
||||
:close-on-select="true">
|
||||
<template #item="{ item, selected }">
|
||||
<div class="flex w-full items-center gap-2">
|
||||
<img :src="item.iconUrl" loading="lazy" class="size-5 rounded-sm" />
|
||||
<span class="truncate text-sm" v-text="item.label"></span>
|
||||
<span v-if="selected" class="ml-auto opacity-70">✓</span>
|
||||
</div>
|
||||
</template>
|
||||
</VirtualCombobox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
@@ -54,9 +54,9 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
|
||||
@@ -5,208 +5,429 @@
|
||||
<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"
|
||||
<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-public-create'
|
||||
) || newInstanceDialog.groupRef.privacy === 'private'
|
||||
'group-instance-age-gated-create'
|
||||
)
|
||||
"
|
||||
>{{ 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="x-friend-item flex w-full items-center">
|
||||
<div class="avatar">
|
||||
<img :src="item.iconUrl" loading="lazy" />
|
||||
@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="x-friend-item flex w-full items-center">
|
||||
<div class="avatar">
|
||||
<img :src="item.iconUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="item.label"></span>
|
||||
</div>
|
||||
<CheckIcon
|
||||
:class="['ml-auto size-4', selected ? 'opacity-100' : 'opacity-0']" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="item.label"></span>
|
||||
</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.userId"
|
||||
: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="x-friend-item flex w-full items-center">
|
||||
<template v-if="item.user">
|
||||
<div class="avatar" :class="userStatusClass(item.user)">
|
||||
<img :src="userImage(item.user)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
class="name"
|
||||
: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>
|
||||
<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">
|
||||
</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="x-friend-item flex w-full items-center">
|
||||
<div class="avatar">
|
||||
<img :src="item.iconUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" 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>
|
||||
@@ -223,220 +444,49 @@
|
||||
<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'
|
||||
</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
|
||||
"
|
||||
class="items-start">
|
||||
<FieldLabel>{{ t('dialog.new_instance.instance_creator') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<VirtualCombobox
|
||||
v-model="newInstanceDialog.userId"
|
||||
: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="x-friend-item flex w-full items-center">
|
||||
<template v-if="item.user">
|
||||
<div class="avatar" :class="userStatusClass(item.user)">
|
||||
<img :src="userImage(item.user)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
class="name"
|
||||
: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="x-friend-item flex w-full items-center">
|
||||
<div class="avatar">
|
||||
<img :src="item.iconUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" 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">
|
||||
@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>
|
||||
@@ -445,7 +495,6 @@
|
||||
}}</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="mr-2"
|
||||
:disabled="
|
||||
(newInstanceDialog.accessType === 'friends' || newInstanceDialog.accessType === 'invite') &&
|
||||
newInstanceDialog.userId !== currentUser.id
|
||||
@@ -469,44 +518,7 @@
|
||||
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.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>
|
||||
</DialogFooter>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
<InviteDialog :invite-dialog="inviteDialog" @closeInviteDialog="closeInviteDialog" />
|
||||
@@ -514,8 +526,15 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '@/components/ui/dialog';
|
||||
import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Check as CheckIcon } from 'lucide-vue-next';
|
||||
|
||||
@@ -4,79 +4,79 @@
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.boop_dialog.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<span>{{ displayName }}</span>
|
||||
<span>{{ displayName }}</span>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<div v-if="sendBoopDialog.visible" style="width: 100%">
|
||||
<VirtualCombobox
|
||||
v-model="emojiModel"
|
||||
:groups="emojiPickerGroups"
|
||||
:placeholder="t('dialog.boop_dialog.select_default_emoji')"
|
||||
:search-placeholder="t('dialog.boop_dialog.select_default_emoji')"
|
||||
:clearable="true"
|
||||
:close-on-select="true"
|
||||
:deselect-on-reselect="true">
|
||||
<template #item="{ item, selected }">
|
||||
<span v-text="item.label"></span>
|
||||
<CheckIcon :class="['ml-auto size-4', selected ? 'opacity-100' : 'opacity-0']" />
|
||||
</template>
|
||||
</VirtualCombobox>
|
||||
</div>
|
||||
<div v-if="sendBoopDialog.visible" style="width: 100%">
|
||||
<VirtualCombobox
|
||||
v-model="emojiModel"
|
||||
:groups="emojiPickerGroups"
|
||||
:placeholder="t('dialog.boop_dialog.select_default_emoji')"
|
||||
:search-placeholder="t('dialog.boop_dialog.select_default_emoji')"
|
||||
:clearable="true"
|
||||
:close-on-select="true"
|
||||
:deselect-on-reselect="true">
|
||||
<template #item="{ item, selected }">
|
||||
<span v-text="item.label"></span>
|
||||
<CheckIcon :class="['ml-auto size-4', selected ? 'opacity-100' : 'opacity-0']" />
|
||||
</template>
|
||||
</VirtualCombobox>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<div
|
||||
v-if="isLocalUserVrcPlusSupporter"
|
||||
style="
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 10px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
">
|
||||
<div
|
||||
v-for="image in emojiTable"
|
||||
:key="image.id"
|
||||
:class="image.id === fileId ? 'x-image-selected' : ''"
|
||||
style="cursor: pointer; border: 1px solid transparent; border-radius: 8px"
|
||||
@click="fileId = image.id">
|
||||
v-if="isLocalUserVrcPlusSupporter"
|
||||
style="
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 10px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
">
|
||||
<div
|
||||
v-if="
|
||||
image.versions &&
|
||||
image.versions.length > 0 &&
|
||||
image.versions[image.versions.length - 1].file.url
|
||||
"
|
||||
class="x-popover-image"
|
||||
style="padding: 8px">
|
||||
<Emoji :imageUrl="image.versions[image.versions.length - 1].file.url" :size="100"></Emoji>
|
||||
v-for="image in emojiTable"
|
||||
:key="image.id"
|
||||
:class="image.id === fileId ? 'x-image-selected' : ''"
|
||||
style="cursor: pointer; border: 1px solid transparent; border-radius: 8px"
|
||||
@click="fileId = image.id">
|
||||
<div
|
||||
v-if="
|
||||
image.versions &&
|
||||
image.versions.length > 0 &&
|
||||
image.versions[image.versions.length - 1].file.url
|
||||
"
|
||||
class="x-popover-image"
|
||||
style="padding: 8px">
|
||||
<Emoji :imageUrl="image.versions[image.versions.length - 1].file.url" :size="100"></Emoji>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button size="sm" variant="outline" class="mr-2" @click="showGalleryPage">{{
|
||||
t('dialog.boop_dialog.emoji_manager')
|
||||
}}</Button>
|
||||
<Button size="sm" variant="secondary" class="mr-2" @click="closeDialog">{{
|
||||
t('dialog.boop_dialog.cancel')
|
||||
}}</Button>
|
||||
<Button size="sm" :disabled="!sendBoopDialog.userId" @click="sendBoop">{{
|
||||
t('dialog.boop_dialog.send')
|
||||
}}</Button>
|
||||
<Button size="sm" variant="outline" class="mr-2" @click="showGalleryPage">{{
|
||||
t('dialog.boop_dialog.emoji_manager')
|
||||
}}</Button>
|
||||
<Button size="sm" variant="secondary" class="mr-2" @click="closeDialog">{{
|
||||
t('dialog.boop_dialog.cancel')
|
||||
}}</Button>
|
||||
<Button size="sm" :disabled="!sendBoopDialog.userId" @click="sendBoop">{{
|
||||
t('dialog.boop_dialog.send')
|
||||
}}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Check as CheckIcon } from 'lucide-vue-next';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
|
||||
@@ -64,8 +64,7 @@
|
||||
</template>
|
||||
<Location
|
||||
:location="userDialog.ref.location"
|
||||
:traveling="userDialog.ref.travelingToLocation"
|
||||
style="display: block; margin-top: 5px" />
|
||||
:traveling="userDialog.ref.travelingToLocation" />
|
||||
</div>
|
||||
<div class="x-friend-list" style="flex: 1; margin-top: 10px; max-height: 150px">
|
||||
<div
|
||||
|
||||
@@ -5,86 +5,86 @@
|
||||
<DialogTitle>{{ t('dialog.vrcx_updater.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div v-loading="checkingForVRCXUpdate" style="margin-top: 15px">
|
||||
<template v-if="updateInProgress">
|
||||
<Progress :model-value="updateProgress" class="w-full" />
|
||||
<div class="mt-2 text-xs" v-text="updateProgressText()"></div>
|
||||
<br />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="VRCXUpdateDialog.updatePending" style="margin-bottom: 15px">
|
||||
<span>{{ pendingVRCXInstall }}</span>
|
||||
<template v-if="updateInProgress">
|
||||
<Progress :model-value="updateProgress" class="w-full" />
|
||||
<div class="mt-2 text-xs" v-text="updateProgressText()"></div>
|
||||
<br />
|
||||
<span>{{ t('dialog.vrcx_updater.ready_for_update') }}</span>
|
||||
</div>
|
||||
<Tabs :model-value="branch" class="w-full" @update:modelValue="handleBranchChange">
|
||||
<TabsList class="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="Stable">{{ t('dialog.vrcx_updater.branch_stable') }}</TabsTrigger>
|
||||
<TabsTrigger value="Nightly">{{ t('dialog.vrcx_updater.branch_nightly') }}</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="Nightly">
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle class="text-muted-foreground" />
|
||||
<AlertTitle>{{ t('dialog.vrcx_updater.nightly_title') }}</AlertTitle>
|
||||
<AlertDescription>
|
||||
{{ t('dialog.vrcx_updater.nightly_notice') }}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<FieldGroup class="mt-3">
|
||||
<Field>
|
||||
<FieldLabel>{{ t('dialog.vrcx_updater.release') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<Select
|
||||
:model-value="VRCXUpdateDialog.release"
|
||||
@update:modelValue="(v) => (VRCXUpdateDialog.release = v)">
|
||||
<SelectTrigger class="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem
|
||||
v-for="item in VRCXUpdateDialog.releases"
|
||||
:key="item.name"
|
||||
:value="item.name">
|
||||
{{ item.tag_name }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<div
|
||||
v-if="!VRCXUpdateDialog.updatePending && VRCXUpdateDialog.release === appVersion"
|
||||
class="mt-3 text-xs text-muted-foreground">
|
||||
<span>{{ t('dialog.vrcx_updater.latest_version') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="VRCXUpdateDialog.updatePending" style="margin-bottom: 15px">
|
||||
<span>{{ pendingVRCXInstall }}</span>
|
||||
<br />
|
||||
<span>{{ t('dialog.vrcx_updater.ready_for_update') }}</span>
|
||||
</div>
|
||||
<Tabs :model-value="branch" class="w-full" @update:modelValue="handleBranchChange">
|
||||
<TabsList class="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="Stable">{{ t('dialog.vrcx_updater.branch_stable') }}</TabsTrigger>
|
||||
<TabsTrigger value="Nightly">{{ t('dialog.vrcx_updater.branch_nightly') }}</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="Nightly">
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle class="text-muted-foreground" />
|
||||
<AlertTitle>{{ t('dialog.vrcx_updater.nightly_title') }}</AlertTitle>
|
||||
<AlertDescription>
|
||||
{{ t('dialog.vrcx_updater.nightly_notice') }}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<FieldGroup class="mt-3">
|
||||
<Field>
|
||||
<FieldLabel>{{ t('dialog.vrcx_updater.release') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<Select
|
||||
:model-value="VRCXUpdateDialog.release"
|
||||
@update:modelValue="(v) => (VRCXUpdateDialog.release = v)">
|
||||
<SelectTrigger class="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem
|
||||
v-for="item in VRCXUpdateDialog.releases"
|
||||
:key="item.name"
|
||||
:value="item.name">
|
||||
{{ item.tag_name }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<div
|
||||
v-if="!VRCXUpdateDialog.updatePending && VRCXUpdateDialog.release === appVersion"
|
||||
class="mt-3 text-xs text-muted-foreground">
|
||||
<span>{{ t('dialog.vrcx_updater.latest_version') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" class="mr-2" v-if="updateInProgress" @click="cancelUpdate">
|
||||
{{ t('dialog.vrcx_updater.cancel') }}
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
v-if="VRCXUpdateDialog.release !== pendingVRCXInstall"
|
||||
:disabled="updateInProgress"
|
||||
@click="installVRCXUpdate">
|
||||
{{ t('dialog.vrcx_updater.download') }}
|
||||
</Button>
|
||||
<Button variant="default" v-if="!updateInProgress && pendingVRCXInstall" @click="restartVRCX(true)">
|
||||
{{ t('dialog.vrcx_updater.install') }}
|
||||
</Button>
|
||||
<Button variant="secondary" class="mr-2" v-if="updateInProgress" @click="cancelUpdate">
|
||||
{{ t('dialog.vrcx_updater.cancel') }}
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
v-if="VRCXUpdateDialog.release !== pendingVRCXInstall"
|
||||
:disabled="updateInProgress"
|
||||
@click="installVRCXUpdate">
|
||||
{{ t('dialog.vrcx_updater.download') }}
|
||||
</Button>
|
||||
<Button variant="default" v-if="!updateInProgress && pendingVRCXInstall" @click="restartVRCX(true)">
|
||||
{{ t('dialog.vrcx_updater.install') }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { AlertCircle } from 'lucide-vue-next';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
|
||||
Reference in New Issue
Block a user