replace el-dialog

This commit is contained in:
pa
2026-01-15 15:33:20 +09:00
committed by Natsumi
parent fc13dca0a4
commit 3b47d3a0eb
67 changed files with 7034 additions and 6769 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,15 @@
<template> <template>
<el-dialog <Dialog
class="x-dialog" :open="changeAvatarImageDialogVisible"
:model-value="changeAvatarImageDialogVisible" @update:open="(open) => {
:title="t('dialog.change_content_image.avatar')" if (!open) closeDialog();
width="850px" }">
append-to-body <DialogContent class="x-dialog sm:max-w-212.5">
@close="closeDialog"> <DialogHeader>
<div> <DialogTitle>{{ t('dialog.change_content_image.avatar') }}</DialogTitle>
</DialogHeader>
<div>
<input <input
id="AvatarImageUploadButton" id="AvatarImageUploadButton"
type="file" type="file"
@@ -27,12 +30,14 @@
<div class="x-change-image-item"> <div class="x-change-image-item">
<img :src="previousImageUrl" class="img-size" loading="lazy" /> <img :src="previousImageUrl" class="img-size" loading="lazy" />
</div> </div>
</div> </div>
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Upload } from 'lucide-vue-next'; import { Upload } from 'lucide-vue-next';
import { ref } from 'vue'; import { ref } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';

View File

@@ -1,14 +1,16 @@
<template> <template>
<el-dialog <Dialog
ref="setAvatarStylesDialog" :open="setAvatarStylesDialog.visible"
class="x-dialog" @update:open="(open) => {
:model-value="setAvatarStylesDialog.visible" if (!open) closeSetAvatarStylesDialog();
:title="t('dialog.set_avatar_styles.header')" }">
width="400px" <DialogContent class="x-dialog sm:max-w-100">
append-to-body <DialogHeader>
@close="closeSetAvatarStylesDialog"> <DialogTitle>{{ t('dialog.set_avatar_styles.header') }}</DialogTitle>
<template v-if="setAvatarStylesDialog.visible"> </DialogHeader>
<div>
<template v-if="setAvatarStylesDialog.visible">
<div>
<span>{{ t('dialog.set_avatar_styles.primary_style') }}</span> <span>{{ t('dialog.set_avatar_styles.primary_style') }}</span>
<br /> <br />
<Select <Select
@@ -64,21 +66,23 @@
style="margin-top: 10px" style="margin-top: 10px"
input-class="resize-none" input-class="resize-none"
@update:modelValue="(v) => updateDialog({ authorTags: v })" /> @update:modelValue="(v) => updateDialog({ authorTags: v })" />
</template> </template>
<template #footer> <DialogFooter>
<Button variant="secondary" class="mr-2" @click="closeSetAvatarStylesDialog">{{ <Button variant="secondary" class="mr-2" @click="closeSetAvatarStylesDialog">{{
t('dialog.set_avatar_styles.cancel') t('dialog.set_avatar_styles.cancel')
}}</Button> }}</Button>
<Button @click="saveSetAvatarStylesDialog"> <Button @click="saveSetAvatarStylesDialog">
{{ t('dialog.set_avatar_styles.save') }} {{ t('dialog.set_avatar_styles.save') }}
</Button> </Button>
</template> </DialogFooter>
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';

View File

@@ -1,102 +1,115 @@
<template> <template>
<el-dialog <Dialog
ref="setAvatarTagsDialog" :open="setAvatarTagsDialog.visible"
class="x-dialog" @update:open="
:model-value="setAvatarTagsDialog.visible" (open) => {
@close="closeSetAvatarTagsDialog" if (!open) closeSetAvatarTagsDialog();
:title="t('dialog.set_avatar_tags.header')" }
width="780px" ">
append-to-body> <DialogContent class="x-dialog sm:max-w-195">
<template v-if="setAvatarTagsDialog.visible"> <DialogHeader>
<label class="inline-flex items-center gap-2"> <DialogTitle>{{ t('dialog.set_avatar_tags.header') }}</DialogTitle>
<Checkbox v-model="setAvatarTagsDialog.contentHorror" @update:modelValue="updateSelectedAvatarTags" /> </DialogHeader>
<span>{{ t('dialog.set_avatar_tags.content_horror') }}</span>
</label> <template v-if="setAvatarTagsDialog.visible">
<label class="inline-flex items-center gap-2"> <label class="inline-flex items-center gap-2">
<Checkbox v-model="setAvatarTagsDialog.contentGore" @update:modelValue="updateSelectedAvatarTags" /> <Checkbox
<span>{{ t('dialog.set_avatar_tags.content_gore') }}</span> v-model="setAvatarTagsDialog.contentHorror"
</label> @update:modelValue="updateSelectedAvatarTags" />
<label class="inline-flex items-center gap-2"> <span>{{ t('dialog.set_avatar_tags.content_horror') }}</span>
<Checkbox v-model="setAvatarTagsDialog.contentViolence" @update:modelValue="updateSelectedAvatarTags" /> </label>
<span>{{ t('dialog.set_avatar_tags.content_violence') }}</span> <label class="inline-flex items-center gap-2">
</label> <Checkbox v-model="setAvatarTagsDialog.contentGore" @update:modelValue="updateSelectedAvatarTags" />
<label class="inline-flex items-center gap-2"> <span>{{ t('dialog.set_avatar_tags.content_gore') }}</span>
<Checkbox v-model="setAvatarTagsDialog.contentAdult" @update:modelValue="updateSelectedAvatarTags" /> </label>
<span>{{ t('dialog.set_avatar_tags.content_adult') }}</span> <label class="inline-flex items-center gap-2">
</label> <Checkbox
<label class="inline-flex items-center gap-2"> v-model="setAvatarTagsDialog.contentViolence"
<Checkbox v-model="setAvatarTagsDialog.contentSex" @update:modelValue="updateSelectedAvatarTags" /> @update:modelValue="updateSelectedAvatarTags" />
<span>{{ t('dialog.set_avatar_tags.content_sex') }}</span> <span>{{ t('dialog.set_avatar_tags.content_violence') }}</span>
</label> </label>
<br /> <label class="inline-flex items-center gap-2">
<InputGroupTextareaField <Checkbox
v-model="setAvatarTagsDialog.selectedTagsCsv" v-model="setAvatarTagsDialog.contentAdult"
:rows="2" @update:modelValue="updateSelectedAvatarTags" />
:placeholder="t('dialog.set_avatar_tags.custom_tags_placeholder')" <span>{{ t('dialog.set_avatar_tags.content_adult') }}</span>
style="margin-top: 10px" </label>
input-class="resize-none" <label class="inline-flex items-center gap-2">
@input="updateInputAvatarTags" /> <Checkbox v-model="setAvatarTagsDialog.contentSex" @update:modelValue="updateSelectedAvatarTags" />
<br /> <span>{{ t('dialog.set_avatar_tags.content_sex') }}</span>
<br /> </label>
<template <br />
v-if="setAvatarTagsDialog.ownAvatars.length === props.setAvatarTagsDialog.selectedAvatarIds.length"> <InputGroupTextareaField
<Button size="sm" variant="outline" @click="setAvatarTagsSelectToggle">{{ v-model="setAvatarTagsDialog.selectedTagsCsv"
t('dialog.set_avatar_tags.select_none') :rows="2"
}}</Button> :placeholder="t('dialog.set_avatar_tags.custom_tags_placeholder')"
</template> style="margin-top: 10px"
<template v-else> input-class="resize-none"
<Button size="sm" variant="outline" @click="setAvatarTagsSelectToggle">{{ @input="updateInputAvatarTags" />
t('dialog.set_avatar_tags.select_all') <br />
}}</Button> <br />
</template> <template
<span style="margin-left: 5px" v-if="setAvatarTagsDialog.ownAvatars.length === props.setAvatarTagsDialog.selectedAvatarIds.length">
>{{ props.setAvatarTagsDialog.selectedAvatarIds.length }} / <Button size="sm" variant="outline" @click="setAvatarTagsSelectToggle">{{
{{ setAvatarTagsDialog.ownAvatars.length }}</span t('dialog.set_avatar_tags.select_none')
> }}</Button>
<Loader2 v-if="setAvatarTagsDialog.loading" class="is-loading" style="margin-left: 5px" /> </template>
<br /> <template v-else>
<div class="x-friend-list" style="margin-top: 10px; min-height: 60px; max-height: 280px"> <Button size="sm" variant="outline" @click="setAvatarTagsSelectToggle">{{
<div t('dialog.set_avatar_tags.select_all')
v-for="avatar in setAvatarTagsDialog.ownAvatars" }}</Button>
:key="avatar.id" </template>
:class="['item-width', 'x-friend-item', 'x-friend-item-border']" <span style="margin-left: 5px"
@click="showAvatarDialog(avatar.id)"> >{{ props.setAvatarTagsDialog.selectedAvatarIds.length }} /
<div class="avatar"> {{ setAvatarTagsDialog.ownAvatars.length }}</span
<img v-if="avatar.thumbnailImageUrl" :src="avatar.thumbnailImageUrl" loading="lazy" /> >
<Loader2 v-if="setAvatarTagsDialog.loading" class="is-loading" style="margin-left: 5px" />
<br />
<div class="x-friend-list" style="margin-top: 10px; min-height: 60px; max-height: 280px">
<div
v-for="avatar in setAvatarTagsDialog.ownAvatars"
:key="avatar.id"
:class="['item-width', 'x-friend-item', 'x-friend-item-border']"
@click="showAvatarDialog(avatar.id)">
<div class="avatar">
<img v-if="avatar.thumbnailImageUrl" :src="avatar.thumbnailImageUrl" loading="lazy" />
</div>
<div class="detail">
<span class="name" v-text="avatar.name"></span>
<span
v-if="avatar.releaseStatus === 'public'"
class="extra"
style="color: var(--el-color-success)"
v-text="avatar.releaseStatus"></span>
<span
v-else-if="avatar.releaseStatus === 'private'"
class="extra"
style="color: var(--el-color-danger)"
v-text="avatar.releaseStatus"></span>
<span v-else class="extra" v-text="avatar.releaseStatus"></span>
<span class="extra" v-text="avatarTagStrings.get(avatar.id)"></span>
</div>
<Button size="sm" variant="ghost" style="margin-left: 5px" @click.stop>
<Checkbox
:model-value="props.setAvatarTagsDialog.selectedAvatarIds.includes(avatar.id)"
@update:modelValue="(val) => toggleAvatarSelection(avatar.id, val)" />
</Button>
</div> </div>
<div class="detail">
<span class="name" v-text="avatar.name"></span>
<span
v-if="avatar.releaseStatus === 'public'"
class="extra"
style="color: var(--el-color-success)"
v-text="avatar.releaseStatus"></span>
<span
v-else-if="avatar.releaseStatus === 'private'"
class="extra"
style="color: var(--el-color-danger)"
v-text="avatar.releaseStatus"></span>
<span v-else class="extra" v-text="avatar.releaseStatus"></span>
<span class="extra" v-text="avatarTagStrings.get(avatar.id)"></span>
</div>
<Button size="sm" variant="ghost" style="margin-left: 5px" @click.stop>
<Checkbox
:model-value="props.setAvatarTagsDialog.selectedAvatarIds.includes(avatar.id)"
@update:modelValue="(val) => toggleAvatarSelection(avatar.id, val)" />
</Button>
</div> </div>
</div> </template>
</template>
<template #footer> <DialogFooter>
<Button variant="secondary" class="mr-2" @click="closeSetAvatarTagsDialog">{{ <Button variant="secondary" class="mr-2" @click="closeSetAvatarTagsDialog">{{
t('dialog.set_avatar_tags.cancel') t('dialog.set_avatar_tags.cancel')
}}</Button> }}</Button>
<Button @click="saveSetAvatarTagsDialog">{{ t('dialog.set_avatar_tags.save') }}</Button> <Button @click="saveSetAvatarTagsDialog">{{ t('dialog.set_avatar_tags.save') }}</Button>
</template> </DialogFooter>
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';

View File

@@ -1,6 +1,10 @@
<template> <template>
<el-dialog :z-index="favoriteDialogIndex" v-model="isVisible" :title="t('dialog.favorite.header')" width="300px"> <Dialog v-model:open="isVisible">
<div v-loading="loading"> <DialogContent>
<DialogHeader>
<DialogTitle>{{ t('dialog.favorite.header') }}</DialogTitle>
</DialogHeader>
<div v-loading="loading">
<span style="display: block; text-align: center">{{ t('dialog.favorite.vrchat_favorites') }}</span> <span style="display: block; text-align: center">{{ t('dialog.favorite.vrchat_favorites') }}</span>
<template v-if="favoriteDialog.currentGroup && favoriteDialog.currentGroup.key"> <template v-if="favoriteDialog.currentGroup && favoriteDialog.currentGroup.key">
<Button <Button
@@ -23,8 +27,8 @@
{{ group.displayName }} ({{ group.count }} / {{ group.capacity }}) {{ group.displayName }} ({{ group.count }} / {{ group.capacity }})
</Button> </Button>
</template> </template>
</div> </div>
<div v-if="favoriteDialog.type === 'world'" style="margin-top: 20px"> <div v-if="favoriteDialog.type === 'world'" style="margin-top: 20px">
<span style="display: block; text-align: center">{{ t('dialog.favorite.local_favorites') }}</span> <span style="display: block; text-align: center">{{ t('dialog.favorite.local_favorites') }}</span>
<template v-for="group in localWorldFavoriteGroups" :key="group"> <template v-for="group in localWorldFavoriteGroups" :key="group">
<Button <Button
@@ -44,8 +48,8 @@
{{ group }} ({{ localWorldFavGroupLength(group) }}) {{ group }} ({{ localWorldFavGroupLength(group) }})
</Button> </Button>
</template> </template>
</div> </div>
<div v-if="favoriteDialog.type === 'avatar'" style="margin-top: 20px"> <div v-if="favoriteDialog.type === 'avatar'" style="margin-top: 20px">
<span style="text-align: center">{{ t('dialog.favorite.local_avatar_favorites') }}</span> <span style="text-align: center">{{ t('dialog.favorite.local_avatar_favorites') }}</span>
<template v-for="group in localAvatarFavoriteGroups" :key="group"> <template v-for="group in localAvatarFavoriteGroups" :key="group">
<Button <Button
@@ -66,14 +70,16 @@
{{ group }} ({{ localAvatarFavGroupLength(group) }}) {{ group }} ({{ localAvatarFavGroupLength(group) }})
</Button> </Button>
</template> </template>
</div> </div>
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { computed, nextTick, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Check } from 'lucide-vue-next'; import { Check } from 'lucide-vue-next';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@@ -81,7 +87,6 @@
import { useFavoriteStore, useUserStore } from '../../stores'; import { useFavoriteStore, useUserStore } from '../../stores';
import { favoriteRequest } from '../../api'; import { favoriteRequest } from '../../api';
import { getNextDialogIndex } from '../../shared/utils/base/ui';
const { t } = useI18n(); const { t } = useI18n();
@@ -107,7 +112,6 @@
} = favoriteStore; } = favoriteStore;
const { isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore()); const { isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
const favoriteDialogIndex = ref(2000);
const groups = ref([]); const groups = ref([]);
const loading = ref(false); const loading = ref(false);
@@ -123,9 +127,6 @@
(value) => { (value) => {
if (value) { if (value) {
initFavoriteDialog(); initFavoriteDialog();
nextTick(() => {
favoriteDialogIndex.value = getNextDialogIndex();
});
} }
} }
); );

View File

@@ -1,11 +1,9 @@
<template> <template>
<el-dialog <Dialog :open="visible" @update:open="(open) => (open ? null : handleClose())">
:model-value="visible" <DialogContent class="custom-nav-dialog">
class="custom-nav-dialog" <DialogHeader>
:title="t('nav_menu.custom_nav.dialog_title')" <DialogTitle>{{ t('nav_menu.custom_nav.dialog_title') }}</DialogTitle>
width="600px" </DialogHeader>
@close="handleClose"
destroy-on-close>
<div class="custom-nav-dialog__list" v-if="localLayout.length"> <div class="custom-nav-dialog__list" v-if="localLayout.length">
<div <div
v-for="(entry, index) in localLayout" v-for="(entry, index) in localLayout"
@@ -90,7 +88,7 @@
type="warning" type="warning"
:closable="false" :closable="false"
:title="t('nav_menu.custom_nav.invalid_folder')" /> :title="t('nav_menu.custom_nav.invalid_folder')" />
<template #footer> <DialogFooter>
<div class="custom-nav-dialog__footer"> <div class="custom-nav-dialog__footer">
<div class="custom-nav-dialog__footer-left"> <div class="custom-nav-dialog__footer-left">
<Button variant="outline" @click="openFolderEditor()"> <Button variant="outline" @click="openFolderEditor()">
@@ -109,15 +107,21 @@
</Button> </Button>
</div> </div>
</div> </div>
</template> </DialogFooter>
</el-dialog> </DialogContent>
</Dialog>
<el-dialog <Dialog v-model:open="folderEditor.visible">
v-model="folderEditor.visible" <DialogContent class="folder-editor-dialog">
class="folder-editor-dialog" <DialogHeader>
:title="folderEditor.isEditing ? t('nav_menu.custom_nav.edit_folder') : t('nav_menu.custom_nav.add_folder')" <DialogTitle>
width="900px" {{
destroy-on-close> folderEditor.isEditing
? t('nav_menu.custom_nav.edit_folder')
: t('nav_menu.custom_nav.add_folder')
}}
</DialogTitle>
</DialogHeader>
<div class="folder-editor"> <div class="folder-editor">
<div class="folder-editor__form"> <div class="folder-editor__form">
<InputGroupField <InputGroupField
@@ -189,7 +193,7 @@
</div> </div>
</div> </div>
</div> </div>
<template #footer> <DialogFooter>
<div class="folder-editor__footer"> <div class="folder-editor__footer">
<Button <Button
variant="destructive" variant="destructive"
@@ -206,13 +210,15 @@
{{ t('nav_menu.custom_nav.save') }} {{ t('nav_menu.custom_nav.save') }}
</Button> </Button>
</div> </div>
</template> </DialogFooter>
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { computed, reactive, ref, watch } from 'vue'; import { computed, reactive, ref, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import dayjs from 'dayjs'; import dayjs from 'dayjs';

View File

@@ -1,61 +1,66 @@
<template> <template>
<el-dialog <Dialog v-model:open="gallerySelectDialog.visible">
class="x-dialog" <DialogContent class="x-dialog w-full sm:max-w-none">
v-model="gallerySelectDialog.visible" <DialogHeader>
:title="t('dialog.gallery_select.header')" <DialogTitle>{{ t('dialog.gallery_select.header') }}</DialogTitle>
width="100%" </DialogHeader>
append-to-body>
<div> <div>
<span>{{ t('dialog.gallery_select.gallery') }}</span> <span>{{ t('dialog.gallery_select.gallery') }}</span>
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{ galleryTable.length }}/64</span> <span style="color: #909399; font-size: 12px; margin-left: 5px">{{ galleryTable.length }}/64</span>
<br /> <br />
<input <input
id="GalleryUploadButton" id="GalleryUploadButton"
type="file" type="file"
accept="image/*" accept="image/*"
style="display: none" style="display: none"
@change="onFileChangeGallery" /> @change="onFileChangeGallery" />
<ButtonGroup> <ButtonGroup>
<Button variant="outline" size="sm" @click="selectImageGallerySelect('', '')"> <Button variant="outline" size="sm" @click="selectImageGallerySelect('', '')">
<X /> <X />
{{ t('dialog.gallery_select.none') }} {{ t('dialog.gallery_select.none') }}
</Button> </Button>
<Button variant="outline" size="sm" @click="refreshGalleryTable"> <Button variant="outline" size="sm" @click="refreshGalleryTable">
<RefreshCw /> <RefreshCw />
{{ t('dialog.gallery_select.refresh') }} {{ t('dialog.gallery_select.refresh') }}
</Button> </Button>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
:disabled="!isLocalUserVrcPlusSupporter" :disabled="!isLocalUserVrcPlusSupporter"
@click="displayGalleryUpload"> @click="displayGalleryUpload">
<Upload /> <Upload />
{{ t('dialog.gallery_select.upload') }} {{ t('dialog.gallery_select.upload') }}
</Button> </Button>
</ButtonGroup> </ButtonGroup>
<br /> <br />
<div <div
v-for="image in galleryTable" v-for="image in galleryTable"
:key="image.id" :key="image.id"
class="x-friend-item" class="x-friend-item"
style="display: inline-block; margin-top: 10px; width: unset; cursor: default"> style="display: inline-block; margin-top: 10px; width: unset; cursor: default">
<template v-if="image.versions && image.versions.length > 0"> <template v-if="image.versions && image.versions.length > 0">
<div <div
v-if="image.versions[image.versions.length - 1].file.url" v-if="image.versions[image.versions.length - 1].file.url"
class="vrcplus-icon" class="vrcplus-icon"
@click="selectImageGallerySelect(image.versions[image.versions.length - 1].file.url, image.id)"> @click="
<img selectImageGallerySelect(image.versions[image.versions.length - 1].file.url, image.id)
:src="image.versions[image.versions.length - 1].file.url" ">
class="avatar" <img
loading="lazy" /></div :src="image.versions[image.versions.length - 1].file.url"
></template> class="avatar"
loading="lazy" />
</div>
</template>
</div>
</div> </div>
</div> </DialogContent>
</el-dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import { X, RefreshCw, Upload } from 'lucide-vue-next'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { RefreshCw, Upload, X } from 'lucide-vue-next';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { ButtonGroup } from '@/components/ui/button-group'; import { ButtonGroup } from '@/components/ui/button-group';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';

File diff suppressed because it is too large Load Diff

View File

@@ -1,34 +1,41 @@
<template> <template>
<el-dialog <Dialog
class="x-dialog" :open="isGroupLogsExportDialogVisible"
:model-value="isGroupLogsExportDialogVisible" @update:open="
:title="t('dialog.group_member_moderation.export_logs')" (open) => {
width="650px" if (!open) setIsGroupLogsExportDialogVisible();
append-to-body }
@close="setIsGroupLogsExportDialogVisible"> ">
<div style="margin-bottom: 10px" class="flex flex-col gap-2"> <DialogContent class="x-dialog sm:max-w-162.5">
<label <DialogHeader>
v-for="option in checkGroupsLogsExportLogsOptions" <DialogTitle>{{ t('dialog.group_member_moderation.export_logs') }}</DialogTitle>
:key="option.label" </DialogHeader>
class="inline-flex items-center gap-2">
<Checkbox <div style="margin-bottom: 10px" class="flex flex-col gap-2">
:model-value="checkedGroupLogsExportLogsOptions.includes(option.label)" <label
@update:modelValue="(val) => toggleGroupLogsExportOption(option.label, val)" /> v-for="option in checkGroupsLogsExportLogsOptions"
<span>{{ t(option.text) }}</span> :key="option.label"
</label> class="inline-flex items-center gap-2">
</div> <Checkbox
<br /> :model-value="checkedGroupLogsExportLogsOptions.includes(option.label)"
<InputGroupTextareaField @update:modelValue="(val) => toggleGroupLogsExportOption(option.label, val)" />
v-model="groupLogsExportContent" <span>{{ t(option.text) }}</span>
:rows="15" </label>
readonly </div>
style="margin-top: 15px" <br />
input-class="resize-none" <InputGroupTextareaField
@click="handleCopyGroupLogsExportContent" /> v-model="groupLogsExportContent"
</el-dialog> :rows="15"
readonly
style="margin-top: 15px"
input-class="resize-none"
@click="handleCopyGroupLogsExportContent" />
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';

View File

@@ -1,131 +1,144 @@
<template> <template>
<el-dialog <Dialog v-model:open="groupPostEditDialog.visible">
v-model="groupPostEditDialog.visible" <DialogContent class="sm:max-w-162.5">
:title="t('dialog.group_post_edit.header')" <DialogHeader>
width="650px" <DialogTitle>{{ t('dialog.group_post_edit.header') }}</DialogTitle>
append-to-body> </DialogHeader>
<div v-if="groupPostEditDialog.visible">
<FieldGroup class="gap-4"> <div v-if="groupPostEditDialog.visible">
<Field> <FieldGroup class="gap-4">
<FieldLabel>{{ t('dialog.group_post_edit.title') }}</FieldLabel> <Field>
<FieldContent> <FieldLabel>{{ t('dialog.group_post_edit.title') }}</FieldLabel>
<InputGroupField v-model="groupPostEditDialog.title" size="sm" /> <FieldContent>
</FieldContent> <InputGroupField v-model="groupPostEditDialog.title" size="sm" />
</Field> </FieldContent>
<Field> </Field>
<FieldLabel>{{ t('dialog.group_post_edit.message') }}</FieldLabel> <Field>
<FieldContent> <FieldLabel>{{ t('dialog.group_post_edit.message') }}</FieldLabel>
<InputGroupTextareaField <FieldContent>
v-model="groupPostEditDialog.text" <InputGroupTextareaField
:rows="4" v-model="groupPostEditDialog.text"
style="margin-top: 10px" :rows="4"
input-class="resize-none" /> style="margin-top: 10px"
</FieldContent> input-class="resize-none" />
</Field> </FieldContent>
<Field v-if="!groupPostEditDialog.postId"> </Field>
<FieldLabel class="sr-only">{{ t('dialog.group_post_edit.send_notification') }}</FieldLabel> <Field v-if="!groupPostEditDialog.postId">
<FieldContent> <FieldLabel class="sr-only">{{ t('dialog.group_post_edit.send_notification') }}</FieldLabel>
<label class="inline-flex items-center gap-2"> <FieldContent>
<Checkbox v-model="groupPostEditDialog.sendNotification" /> <label class="inline-flex items-center gap-2">
<span>{{ t('dialog.group_post_edit.send_notification') }}</span> <Checkbox v-model="groupPostEditDialog.sendNotification" />
</label> <span>{{ t('dialog.group_post_edit.send_notification') }}</span>
</FieldContent> </label>
</Field> </FieldContent>
<Field> </Field>
<FieldLabel>{{ t('dialog.group_post_edit.post_visibility') }}</FieldLabel> <Field>
<FieldContent> <FieldLabel>{{ t('dialog.group_post_edit.post_visibility') }}</FieldLabel>
<RadioGroup v-model="groupPostEditDialog.visibility" class="flex items-center gap-4"> <FieldContent>
<div class="flex items-center space-x-2"> <RadioGroup v-model="groupPostEditDialog.visibility" class="flex items-center gap-4">
<RadioGroupItem id="groupPostVisibility-public" value="public" /> <div class="flex items-center space-x-2">
<label for="groupPostVisibility-public"> <RadioGroupItem id="groupPostVisibility-public" value="public" />
{{ t('dialog.group_post_edit.visibility_public') }} <label for="groupPostVisibility-public">
</label> {{ t('dialog.group_post_edit.visibility_public') }}
</div> </label>
<div class="flex items-center space-x-2"> </div>
<RadioGroupItem id="groupPostVisibility-group" value="group" /> <div class="flex items-center space-x-2">
<label for="groupPostVisibility-group"> <RadioGroupItem id="groupPostVisibility-group" value="group" />
{{ t('dialog.group_post_edit.visibility_group') }} <label for="groupPostVisibility-group">
</label> {{ t('dialog.group_post_edit.visibility_group') }}
</div> </label>
</RadioGroup> </div>
</FieldContent> </RadioGroup>
</Field> </FieldContent>
<Field v-if="groupPostEditDialog.visibility === 'group'"> </Field>
<FieldLabel>{{ t('dialog.new_instance.roles') }}</FieldLabel> <Field v-if="groupPostEditDialog.visibility === 'group'">
<FieldContent> <FieldLabel>{{ t('dialog.new_instance.roles') }}</FieldLabel>
<Select <FieldContent>
multiple <Select
:model-value="Array.isArray(groupPostEditDialog.roleIds) ? groupPostEditDialog.roleIds : []" multiple
@update:modelValue="handleRoleIdsChange"> :model-value="
<SelectTrigger size="sm" class="w-full"> Array.isArray(groupPostEditDialog.roleIds) ? groupPostEditDialog.roleIds : []
<SelectValue> "
<span class="truncate"> @update:modelValue="handleRoleIdsChange">
{{ selectedRoleSummary || t('dialog.new_instance.role_placeholder') }} <SelectTrigger size="sm" class="w-full">
</span> <SelectValue>
</SelectValue> <span class="truncate">
</SelectTrigger> {{ selectedRoleSummary || t('dialog.new_instance.role_placeholder') }}
<SelectContent> </span>
<SelectGroup> </SelectValue>
<SelectItem </SelectTrigger>
v-for="role in groupPostEditDialog.groupRef?.roles ?? []" <SelectContent>
:key="role.id" <SelectGroup>
:value="role.id"> <SelectItem
{{ role.name }} v-for="role in groupPostEditDialog.groupRef?.roles ?? []"
</SelectItem> :key="role.id"
</SelectGroup> :value="role.id">
</SelectContent> {{ role.name }}
</Select> </SelectItem>
</FieldContent> </SelectGroup>
</Field> </SelectContent>
<Field> </Select>
<FieldLabel>{{ t('dialog.group_post_edit.image') }}</FieldLabel> </FieldContent>
<FieldContent> </Field>
<template v-if="gallerySelectDialog.selectedFileId"> <Field>
<div style="display: inline-block; flex: none; margin-right: 5px"> <FieldLabel>{{ t('dialog.group_post_edit.image') }}</FieldLabel>
<img <FieldContent>
:src="gallerySelectDialog.selectedImageUrl" <template v-if="gallerySelectDialog.selectedFileId">
style="flex: none; width: 60px; height: 60px; border-radius: 4px; object-fit: cover" <div style="display: inline-block; flex: none; margin-right: 5px">
@click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)" <img
loading="lazy" /> :src="gallerySelectDialog.selectedImageUrl"
<Button style="
size="sm" flex: none;
variant="outline" width: 60px;
style="vertical-align: top" height: 60px;
@click="clearImageGallerySelect"> border-radius: 4px;
{{ t('dialog.invite_message.clear_selected_image') }} object-fit: cover;
"
@click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)"
loading="lazy" />
<Button
size="sm"
variant="outline"
style="vertical-align: top"
@click="clearImageGallerySelect">
{{ t('dialog.invite_message.clear_selected_image') }}
</Button>
</div>
</template>
<template v-else>
<Button size="sm" variant="outline" @click="showGallerySelectDialog">
{{ t('dialog.invite_message.select_image') }}
</Button> </Button>
</div> </template>
</template> </FieldContent>
<template v-else> </Field>
<Button size="sm" variant="outline" @click="showGallerySelectDialog"> </FieldGroup>
{{ t('dialog.invite_message.select_image') }}
</Button>
</template>
</FieldContent>
</Field>
</FieldGroup>
</div>
<template #footer>
<div class="flex gap-2">
<Button variant="secondary" @click="groupPostEditDialog.visible = false">
{{ t('dialog.group_post_edit.cancel') }}
</Button>
<Button v-if="groupPostEditDialog.postId" @click="editGroupPost">
{{ t('dialog.group_post_edit.edit_post') }}
</Button>
<Button v-else @click="createGroupPost">
{{ t('dialog.group_post_edit.create_post') }}
</Button>
</div> </div>
</template>
<GallerySelectDialog <DialogFooter>
:gallery-select-dialog="gallerySelectDialog" <div class="flex gap-2">
:gallery-table="galleryTable" <Button variant="secondary" @click="groupPostEditDialog.visible = false">
@refresh-gallery-table="refreshGalleryTable" /> {{ t('dialog.group_post_edit.cancel') }}
</el-dialog> </Button>
<Button v-if="groupPostEditDialog.postId" @click="editGroupPost">
{{ t('dialog.group_post_edit.edit_post') }}
</Button>
<Button v-else @click="createGroupPost">
{{ t('dialog.group_post_edit.create_post') }}
</Button>
</div>
</DialogFooter>
<GallerySelectDialog
:gallery-select-dialog="gallerySelectDialog"
:gallery-table="galleryTable"
@refresh-gallery-table="refreshGalleryTable" />
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field'; import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field';
import { InputGroupField, InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupField, InputGroupTextareaField } from '@/components/ui/input-group';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';

View File

@@ -1,35 +1,42 @@
<template> <template>
<el-dialog <Dialog
class="x-dialog" :open="editAndSendInviteDialog.visible"
:model-value="editAndSendInviteDialog.visible" @update:open="
:title="t('dialog.edit_send_invite_message.header')" (open) => {
width="400px" if (!open) cancelEditAndSendInvite();
append-to-body }
@close="cancelEditAndSendInvite"> ">
<div style="font-size: 12px"> <DialogContent class="x-dialog sm:max-w-100">
<span>{{ t('dialog.edit_send_invite_message.description') }}</span> <DialogHeader>
</div> <DialogTitle>{{ t('dialog.edit_send_invite_message.header') }}</DialogTitle>
</DialogHeader>
<InputGroupTextareaField <div style="font-size: 12px">
v-model="editAndSendInviteDialog.newMessage" <span>{{ t('dialog.edit_send_invite_message.description') }}</span>
:maxlength="64" </div>
:rows="2"
class="mt-2.5"
placeholder=""
show-count />
<template #footer> <InputGroupTextareaField
<Button variant="secondary" class="mr-2" @click="cancelEditAndSendInvite"> v-model="editAndSendInviteDialog.newMessage"
{{ t('dialog.edit_send_invite_message.cancel') }} :maxlength="64"
</Button> :rows="2"
<Button @click="saveEditAndSendInvite" :disabled="!editAndSendInviteDialog.newMessage"> class="mt-2.5"
{{ t('dialog.edit_send_invite_message.send') }} placeholder=""
</Button> show-count />
</template>
</el-dialog> <DialogFooter>
<Button variant="secondary" class="mr-2" @click="cancelEditAndSendInvite">
{{ t('dialog.edit_send_invite_message.cancel') }}
</Button>
<Button @click="saveEditAndSendInvite" :disabled="!editAndSendInviteDialog.newMessage">
{{ t('dialog.edit_send_invite_message.send') }}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';

View File

@@ -1,89 +1,97 @@
<template> <template>
<el-dialog <Dialog
class="x-dialog" :open="inviteDialog.visible"
:model-value="inviteDialog.visible" @update:open="
@close="closeInviteDialog" (open) => {
:title="t('dialog.invite.header')" if (!open) closeInviteDialog();
width="500px" }
append-to-body> ">
<div v-if="inviteDialog.visible" v-loading="inviteDialog.loading"> <DialogContent class="x-dialog sm:max-w-125">
<Location :location="inviteDialog.worldId" :link="false" /> <DialogHeader>
<br /> <DialogTitle>{{ t('dialog.invite.header') }}</DialogTitle>
<Button size="sm" class="mr-2" variant="outline" style="margin-top: 10px" @click="addSelfToInvite">{{ </DialogHeader>
t('dialog.invite.add_self')
}}</Button>
<Button
size="sm"
class="mr-2"
variant="outline"
:disabled="inviteDialog.friendsInInstance.length === 0"
style="margin-top: 10px"
@click="addFriendsInInstanceToInvite"
>{{ t('dialog.invite.add_friends_in_instance') }}</Button
>
<Button
size="sm"
variant="outline"
:disabled="vipFriends.length === 0"
style="margin-top: 10px"
@click="addFavoriteFriendsToInvite"
>{{ t('dialog.invite.add_favorite_friends') }}</Button
>
<div style="width: 100%; margin-top: 15px"> <div v-if="inviteDialog.visible" v-loading="inviteDialog.loading">
<VirtualCombobox <Location :location="inviteDialog.worldId" :link="false" />
:model-value="Array.isArray(inviteDialog.userIds) ? inviteDialog.userIds : []" <br />
@update:modelValue="setInviteUserIds" <Button size="sm" class="mr-2" variant="outline" style="margin-top: 10px" @click="addSelfToInvite">{{
:groups="userPickerGroups" t('dialog.invite.add_self')
multiple }}</Button>
:disabled="inviteDialog.loading" <Button
:placeholder="t('dialog.invite.select_placeholder')" size="sm"
:search-placeholder="t('dialog.invite.select_placeholder')" class="mr-2"
:clearable="true"> variant="outline"
<template #item="{ item, selected }"> :disabled="inviteDialog.friendsInInstance.length === 0"
<div class="x-friend-item flex w-full items-center"> style="margin-top: 10px"
<template v-if="item.user"> @click="addFriendsInInstanceToInvite"
<div :class="['avatar', userStatusClass(item.user)]"> >{{ t('dialog.invite.add_friends_in_instance') }}</Button
<img :src="userImage(item.user)" loading="lazy" /> >
</div> <Button
<div class="detail"> size="sm"
<span class="name" :style="{ color: item.user.$userColour }">{{ variant="outline"
item.user.displayName :disabled="vipFriends.length === 0"
}}</span> style="margin-top: 10px"
</div> @click="addFavoriteFriendsToInvite"
</template> >{{ t('dialog.invite.add_favorite_friends') }}</Button
<template v-else> >
<span>{{ item.label }}</span>
</template>
<CheckIcon :class="['ml-auto size-4', selected ? 'opacity-100' : 'opacity-0']" /> <div style="width: 100%; margin-top: 15px">
</div> <VirtualCombobox
</template> :model-value="Array.isArray(inviteDialog.userIds) ? inviteDialog.userIds : []"
</VirtualCombobox> @update:modelValue="setInviteUserIds"
:groups="userPickerGroups"
multiple
:disabled="inviteDialog.loading"
:placeholder="t('dialog.invite.select_placeholder')"
:search-placeholder="t('dialog.invite.select_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', userStatusClass(item.user)]">
<img :src="userImage(item.user)" loading="lazy" />
</div>
<div class="detail">
<span class="name" :style="{ color: item.user.$userColour }">{{
item.user.displayName
}}</span>
</div>
</template>
<template v-else>
<span>{{ item.label }}</span>
</template>
<CheckIcon :class="['ml-auto size-4', selected ? 'opacity-100' : 'opacity-0']" />
</div>
</template>
</VirtualCombobox>
</div>
</div> </div>
</div>
<template #footer> <DialogFooter>
<Button <Button
variant="secondary" variant="secondary"
class="mr-2" class="mr-2"
:disabled="inviteDialog.loading || !inviteDialog.userIds.length" :disabled="inviteDialog.loading || !inviteDialog.userIds.length"
@click="showSendInviteDialog" @click="showSendInviteDialog"
>{{ t('dialog.invite.invite_with_message') }}</Button >{{ t('dialog.invite.invite_with_message') }}</Button
> >
<Button :disabled="inviteDialog.loading || !inviteDialog.userIds.length" @click="sendInvite">{{ <Button :disabled="inviteDialog.loading || !inviteDialog.userIds.length" @click="sendInvite">{{
t('dialog.invite.invite') t('dialog.invite.invite')
}}</Button> }}</Button>
</template> </DialogFooter>
</DialogContent>
<SendInviteDialog <SendInviteDialog
v-model:sendInviteDialogVisible="sendInviteDialogVisible" v-model:sendInviteDialogVisible="sendInviteDialogVisible"
v-model:sendInviteDialog="sendInviteDialog" v-model:sendInviteDialog="sendInviteDialog"
:invite-dialog="inviteDialog" :invite-dialog="inviteDialog"
@closeInviteDialog="closeInviteDialog" /> @closeInviteDialog="closeInviteDialog" />
</el-dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Check as CheckIcon } from 'lucide-vue-next'; import { Check as CheckIcon } from 'lucide-vue-next';

View File

@@ -1,27 +1,35 @@
<template> <template>
<el-dialog <Dialog
class="x-dialog" :open="isSendInviteConfirmDialogVisible"
:model-value="isSendInviteConfirmDialogVisible" @update:open="
:title="t('dialog.invite_message.header')" (open) => {
width="400px" if (!open) cancelInviteConfirm();
append-to-body }
@close="cancelInviteConfirm"> ">
<div style="font-size: 12px"> <DialogContent class="x-dialog sm:max-w-100">
<span>{{ t('dialog.invite_message.confirmation') }}</span> <DialogHeader>
</div> <DialogTitle>{{ t('dialog.invite_message.header') }}</DialogTitle>
</DialogHeader>
<template #footer> <div style="font-size: 12px">
<Button variant="secondary" @click="cancelInviteConfirm"> <span>{{ t('dialog.invite_message.confirmation') }}</span>
{{ t('dialog.invite_message.cancel') }} </div>
</Button>
<Button @click="sendInviteConfirm"> <DialogFooter>
{{ t('dialog.invite_message.confirm') }} <Button variant="secondary" @click="cancelInviteConfirm">
</Button> {{ t('dialog.invite_message.cancel') }}
</template> </Button>
</el-dialog> <Button @click="sendInviteConfirm">
{{ t('dialog.invite_message.confirm') }}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';

View File

@@ -1,55 +1,62 @@
<template> <template>
<el-dialog <Dialog
class="x-dialog" :open="sendInviteDialogVisible"
:model-value="sendInviteDialogVisible" @update:open="
:title="t('dialog.invite_message.header')" (open) => {
width="800px" if (!open) cancelSendInvite();
append-to-body }
@close="cancelSendInvite"> ">
<template v-if="isLocalUserVrcPlusSupporter"> <DialogContent class="x-dialog sm:max-w-200">
<!-- <template v-if="gallerySelectDialog.selectedFileId">--> <DialogHeader>
<!-- <div style="display: inline-block; flex: none; margin-right: 5px">--> <DialogTitle>{{ t('dialog.invite_message.header') }}</DialogTitle>
<!-- <el-popover placement="right" :width="500px" trigger="click">--> </DialogHeader>
<!-- <template #reference>-->
<!-- <img-->
<!-- class="x-link"-->
<!-- :src="gallerySelectDialog.selectedImageUrl"-->
<!-- style="flex: none; width: 60px; height: 60px; border-radius: 4px; object-fit: cover" />-->
<!-- </template>-->
<!-- <img-->
<!-- class="x-link"-->
<!-- :src="gallerySelectDialog.selectedImageUrl"-->
<!-- style="height: 500px"-->
<!-- @click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)" />-->
<!-- </el-popover>-->
<!-- </div>-->
<!-- <el-button size="small" @click="clearImageGallerySelect" style="vertical-align: top">-->
<!-- {{ t('dialog.invite_message.clear_selected_image') }}-->
<!-- </el-button>-->
<!-- </template>-->
<!-- <template v-else>-->
<!-- <el-button size="small" @click="showGallerySelectDialog" style="margin-right: 5px">-->
<!-- {{ t('dialog.invite_message.select_image') }}-->
<!-- </el-button>-->
<!-- </template>-->
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
</template>
<DataTableLayout <template v-if="isLocalUserVrcPlusSupporter">
style="margin-top: 10px" <!-- <template v-if="gallerySelectDialog.selectedFileId">-->
:table="inviteMessageTanstackTable" <!-- <div style="display: inline-block; flex: none; margin-right: 5px">-->
:loading="false" <!-- <el-popover placement="right" :width="500px" trigger="click">-->
:show-pagination="false" <!-- <template #reference>-->
:on-row-click="handleInviteMessageRowClick" /> <!-- <img-->
<!-- class="x-link"-->
<!-- :src="gallerySelectDialog.selectedImageUrl"-->
<!-- style="flex: none; width: 60px; height: 60px; border-radius: 4px; object-fit: cover" />-->
<!-- </template>-->
<!-- <img-->
<!-- class="x-link"-->
<!-- :src="gallerySelectDialog.selectedImageUrl"-->
<!-- style="height: 500px"-->
<!-- @click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)" />-->
<!-- </el-popover>-->
<!-- </div>-->
<!-- <el-button size="small" @click="clearImageGallerySelect" style="vertical-align: top">-->
<!-- {{ t('dialog.invite_message.clear_selected_image') }}-->
<!-- </el-button>-->
<!-- </template>-->
<!-- <template v-else>-->
<!-- <el-button size="small" @click="showGallerySelectDialog" style="margin-right: 5px">-->
<!-- {{ t('dialog.invite_message.select_image') }}-->
<!-- </el-button>-->
<!-- </template>-->
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
</template>
<DataTableLayout
style="margin-top: 10px"
:table="inviteMessageTanstackTable"
:loading="false"
:show-pagination="false"
:on-row-click="handleInviteMessageRowClick" />
<DialogFooter>
<Button variant="secondary" @click="cancelSendInvite">
{{ t('dialog.invite_message.cancel') }}
</Button>
<Button variant="outline" @click="refreshInviteMessageTableData('message')">
{{ t('dialog.invite_message.refresh') }}
</Button>
</DialogFooter>
</DialogContent>
<template #footer>
<Button variant="secondary" @click="cancelSendInvite">
{{ t('dialog.invite_message.cancel') }}
</Button>
<Button variant="outline" @click="refreshInviteMessageTableData('message')">
{{ t('dialog.invite_message.refresh') }}
</Button>
</template>
<SendInviteConfirmDialog <SendInviteConfirmDialog
v-model:isSendInviteConfirmDialogVisible="isSendInviteConfirmDialogVisible" v-model:isSendInviteConfirmDialogVisible="isSendInviteConfirmDialogVisible"
:sendInviteDialog="sendInviteDialog" :sendInviteDialog="sendInviteDialog"
@@ -62,10 +69,11 @@
@update:sendInviteDialog="emit('update:sendInviteDialog', $event)" @update:sendInviteDialog="emit('update:sendInviteDialog', $event)"
:invite-dialog="inviteDialog" :invite-dialog="inviteDialog"
@closeInviteDialog="closeInviteDialog" /> @closeInviteDialog="closeInviteDialog" />
</el-dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { DataTableLayout } from '@/components/ui/data-table'; import { DataTableLayout } from '@/components/ui/data-table';

View File

@@ -1,11 +1,11 @@
<template> <template>
<el-dialog <Dialog v-model:open="inviteGroupDialog.visible">
:z-index="inviteGroupDialogIndex" <DialogContent class="sm:max-w-112.5">
v-model="inviteGroupDialog.visible" <DialogHeader>
:title="t('dialog.invite_to_group.header')" <DialogTitle>{{ t('dialog.invite_to_group.header') }}</DialogTitle>
width="450px" </DialogHeader>
append-to-body>
<div v-if="inviteGroupDialog.visible" v-loading="inviteGroupDialog.loading"> <div v-if="inviteGroupDialog.visible" v-loading="inviteGroupDialog.loading">
<span>{{ t('dialog.invite_to_group.description') }}</span> <span>{{ t('dialog.invite_to_group.description') }}</span>
<br /> <br />
@@ -64,20 +64,25 @@
</template> </template>
</VirtualCombobox> </VirtualCombobox>
</div> </div>
</div> </div>
<template #footer>
<Button <DialogFooter>
:disabled="inviteGroupDialog.loading || !inviteGroupDialog.userIds.length || !inviteGroupDialog.groupId" <Button
@click="sendGroupInvite"> :disabled="
{{ t('dialog.invite_to_group.invite') }} inviteGroupDialog.loading || !inviteGroupDialog.userIds.length || !inviteGroupDialog.groupId
</Button> "
</template> @click="sendGroupInvite">
</el-dialog> {{ t('dialog.invite_to_group.invite') }}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { computed, nextTick, ref, watch } from 'vue'; import { computed, watch } from 'vue';
import { Button } from '@/components/ui/button'; 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 { Check as CheckIcon } from 'lucide-vue-next';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
@@ -87,7 +92,6 @@
import { useFriendStore, useGroupStore, useModalStore } from '../../stores'; import { useFriendStore, useGroupStore, useModalStore } from '../../stores';
import { groupRequest, userRequest } from '../../api'; import { groupRequest, userRequest } from '../../api';
import { VirtualCombobox } from '../ui/virtual-combobox'; import { VirtualCombobox } from '../ui/virtual-combobox';
import { getNextDialogIndex } from '../../shared/utils/base/ui';
import configRepository from '../../service/config'; import configRepository from '../../service/config';
@@ -109,8 +113,6 @@
} }
); );
const inviteGroupDialogIndex = ref(2000);
const groupsWithInvitePermission = computed(() => { const groupsWithInvitePermission = computed(() => {
return Array.from(currentUserGroups.value.values()).filter((group) => return Array.from(currentUserGroups.value.values()).filter((group) =>
hasGroupPermission(group, 'group-invites-manage') hasGroupPermission(group, 'group-invites-manage')
@@ -223,9 +225,6 @@
); );
function initDialog() { function initDialog() {
nextTick(() => {
inviteGroupDialogIndex.value = getNextDialogIndex();
});
const D = inviteGroupDialog.value; const D = inviteGroupDialog.value;
if (D.groupId) { if (D.groupId) {
groupRequest groupRequest

View File

@@ -1,5 +1,9 @@
<template> <template>
<el-dialog :z-index="launchDialogIndex" v-model="isVisible" :title="t('dialog.launch.header')" width="450px"> <Dialog v-model:open="isVisible">
<DialogContent>
<DialogHeader>
<DialogTitle>{{ t('dialog.launch.header') }}</DialogTitle>
</DialogHeader>
<FieldGroup class="gap-4"> <FieldGroup class="gap-4">
<Field> <Field>
<FieldLabel>{{ t('dialog.launch.url') }}</FieldLabel> <FieldLabel>{{ t('dialog.launch.url') }}</FieldLabel>
@@ -63,8 +67,7 @@
</FieldContent> </FieldContent>
</Field> </Field>
</FieldGroup> </FieldGroup>
<template #footer> <DialogFooter>
<div class="flex justify-end">
<Button <Button
class="mr-1.5" class="mr-1.5"
variant="outline" variant="outline"
@@ -117,14 +120,16 @@
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</ButtonGroup> </ButtonGroup>
</div> </DialogFooter>
</template>
<InviteDialog :invite-dialog="inviteDialog" @closeInviteDialog="closeInviteDialog" /> <InviteDialog :invite-dialog="inviteDialog" @closeInviteDialog="closeInviteDialog" />
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'; import { computed, onBeforeUnmount, ref, watch } from 'vue';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@@ -150,7 +155,6 @@
} from '../../stores'; } from '../../stores';
import { checkCanInvite, getLaunchURL, isRealInstance, parseLocation } from '../../shared/utils'; import { checkCanInvite, getLaunchURL, isRealInstance, parseLocation } from '../../shared/utils';
import { instanceRequest, worldRequest } from '../../api'; import { instanceRequest, worldRequest } from '../../api';
import { getNextDialogIndex } from '../../shared/utils/base/ui';
import InviteDialog from './InviteDialog/InviteDialog.vue'; import InviteDialog from './InviteDialog/InviteDialog.vue';
import configRepository from '../../service/config'; import configRepository from '../../service/config';
@@ -171,8 +175,6 @@
launchDialog.value.desktop ? t('dialog.launch.start_as_desktop') : t('dialog.launch.launch') launchDialog.value.desktop ? t('dialog.launch.start_as_desktop') : t('dialog.launch.launch')
); );
const launchDialogIndex = ref(2000);
let launchAsDesktopTimeoutId; let launchAsDesktopTimeoutId;
onBeforeUnmount(() => { onBeforeUnmount(() => {
@@ -312,9 +314,6 @@
if (!isRealInstance(tag)) { if (!isRealInstance(tag)) {
return; return;
} }
nextTick(() => {
launchDialogIndex.value = getNextDialogIndex();
});
const D = launchDialog.value; const D = launchDialog.value;
D.tag = tag; D.tag = tag;
D.secureOrShortName = shortName; D.secureOrShortName = shortName;

View File

@@ -1,11 +1,11 @@
<template> <template>
<el-dialog <Dialog v-model:open="moderateGroupDialog.visible">
:z-index="moderateGroupDialogIndex" <DialogContent class="sm:max-w-112.5">
v-model="moderateGroupDialog.visible" <DialogHeader>
:title="t('dialog.moderate_group.header')" <DialogTitle>{{ t('dialog.moderate_group.header') }}</DialogTitle>
width="450px" </DialogHeader>
append-to-body>
<div v-if="moderateGroupDialog.visible"> <div v-if="moderateGroupDialog.visible">
<div class="x-friend-item" style="cursor: default"> <div class="x-friend-item" style="cursor: default">
<div class="avatar"> <div class="avatar">
<img :src="userImage(moderateGroupDialog.userObject)" loading="lazy" /> <img :src="userImage(moderateGroupDialog.userObject)" loading="lazy" />
@@ -37,30 +37,32 @@
</template> </template>
</VirtualCombobox> </VirtualCombobox>
</div> </div>
</div> </div>
<template #footer>
<Button <DialogFooter>
:disabled="!moderateGroupDialog.userId || !moderateGroupDialog.groupId" <Button
@click=" :disabled="!moderateGroupDialog.userId || !moderateGroupDialog.groupId"
showGroupMemberModerationDialog(moderateGroupDialog.groupId, moderateGroupDialog.userId); @click="
moderateGroupDialog.visible = false; showGroupMemberModerationDialog(moderateGroupDialog.groupId, moderateGroupDialog.userId);
"> moderateGroupDialog.visible = false;
{{ t('dialog.moderate_group.moderation_tools') }} ">
</Button> {{ t('dialog.moderate_group.moderation_tools') }}
</template> </Button>
</el-dialog> </DialogFooter>
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { computed, nextTick, ref, watch } from 'vue'; import { computed, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { groupRequest, userRequest } from '../../api'; import { groupRequest, userRequest } from '../../api';
import { hasGroupModerationPermission, userImage } from '../../shared/utils'; import { hasGroupModerationPermission, userImage } from '../../shared/utils';
import { VirtualCombobox } from '../ui/virtual-combobox'; import { VirtualCombobox } from '../ui/virtual-combobox';
import { getNextDialogIndex } from '../../shared/utils/base/ui';
import { useGroupStore } from '../../stores'; import { useGroupStore } from '../../stores';
const { currentUserGroups, moderateGroupDialog } = storeToRefs(useGroupStore()); const { currentUserGroups, moderateGroupDialog } = storeToRefs(useGroupStore());
@@ -99,12 +101,7 @@
} }
); );
const moderateGroupDialogIndex = ref(2000);
function initDialog() { function initDialog() {
nextTick(() => {
moderateGroupDialogIndex.value = getNextDialogIndex();
});
const D = moderateGroupDialog.value; const D = moderateGroupDialog.value;
if (D.groupId) { if (D.groupId) {
groupRequest groupRequest

View File

@@ -1,10 +1,9 @@
<template> <template>
<el-dialog <Dialog v-model:open="newInstanceDialog.visible">
:z-index="newInstanceDialogIndex" <DialogContent>
v-model="newInstanceDialog.visible" <DialogHeader>
:title="t('dialog.new_instance.header')" <DialogTitle>{{ t('dialog.new_instance.header') }}</DialogTitle>
width="650px" </DialogHeader>
append-to-body>
<TabsUnderline <TabsUnderline
v-model="newInstanceDialog.selectedTab" v-model="newInstanceDialog.selectedTab"
:items="newInstanceTabs" :items="newInstanceTabs"
@@ -435,7 +434,7 @@
</FieldGroup> </FieldGroup>
</template> </template>
</TabsUnderline> </TabsUnderline>
<template v-if="newInstanceDialog.selectedTab === 'Normal'" #footer> <DialogFooter v-if="newInstanceDialog.selectedTab === 'Normal'">
<template v-if="newInstanceDialog.instanceCreated"> <template v-if="newInstanceDialog.instanceCreated">
<Button variant="outline" class="mr-2" @click="copyInstanceUrl(newInstanceDialog.location)">{{ <Button variant="outline" class="mr-2" @click="copyInstanceUrl(newInstanceDialog.location)">{{
t('dialog.new_instance.copy_url') t('dialog.new_instance.copy_url')
@@ -473,8 +472,8 @@
<template v-else> <template v-else>
<Button @click="handleCreateNewInstance">{{ t('dialog.new_instance.create_instance') }}</Button> <Button @click="handleCreateNewInstance">{{ t('dialog.new_instance.create_instance') }}</Button>
</template> </template>
</template> </DialogFooter>
<template v-else-if="newInstanceDialog.selectedTab === 'Legacy'" #footer> <DialogFooter v-else-if="newInstanceDialog.selectedTab === 'Legacy'">
<Button variant="outline" class="mr-2" @click="copyInstanceUrl(newInstanceDialog.location)">{{ <Button variant="outline" class="mr-2" @click="copyInstanceUrl(newInstanceDialog.location)">{{
t('dialog.new_instance.copy_url') t('dialog.new_instance.copy_url')
}}</Button> }}</Button>
@@ -506,14 +505,17 @@
t('dialog.new_instance.launch') t('dialog.new_instance.launch')
}}</Button> }}</Button>
</template> </template>
</template> </DialogFooter>
</DialogContent>
<InviteDialog :invite-dialog="inviteDialog" @closeInviteDialog="closeInviteDialog" /> <InviteDialog :invite-dialog="inviteDialog" @closeInviteDialog="closeInviteDialog" />
</el-dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field'; import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field';
import { computed, nextTick, ref, watch } from 'vue'; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Check as CheckIcon } from 'lucide-vue-next'; import { Check as CheckIcon } from 'lucide-vue-next';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
@@ -545,7 +547,6 @@
import { groupRequest, instanceRequest, worldRequest } from '../../api'; import { groupRequest, instanceRequest, worldRequest } from '../../api';
import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group'; import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group';
import { VirtualCombobox } from '../ui/virtual-combobox'; import { VirtualCombobox } from '../ui/virtual-combobox';
import { getNextDialogIndex } from '../../shared/utils/base/ui';
import InviteDialog from './InviteDialog/InviteDialog.vue'; import InviteDialog from './InviteDialog/InviteDialog.vue';
import configRepository from '../../service/config'; import configRepository from '../../service/config';
@@ -572,8 +573,6 @@
const { currentUser, isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore()); const { currentUser, isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
const { canOpenInstanceInGame } = useInviteStore(); const { canOpenInstanceInGame } = useInviteStore();
const newInstanceDialogIndex = ref(2000);
const newInstanceDialog = ref({ const newInstanceDialog = ref({
visible: false, visible: false,
// loading: false, // loading: false,
@@ -778,9 +777,6 @@
if (!isRealInstance(tag)) { if (!isRealInstance(tag)) {
return; return;
} }
nextTick(() => {
newInstanceDialogIndex.value = getNextDialogIndex();
});
const D = newInstanceDialog.value; const D = newInstanceDialog.value;
const L = parseLocation(tag); const L = parseLocation(tag);
if (D.worldId === L.worldId) { if (D.worldId === L.worldId) {

View File

@@ -1,34 +1,36 @@
<template> <template>
<el-dialog <Dialog v-model:open="isVisible">
:z-index="previousInstancesGroupDialogIndex" <DialogContent class="sm:max-w-250">
v-model="isVisible" <DialogHeader>
:title="t('dialog.previous_instances.header')" <DialogTitle>{{ t('dialog.previous_instances.header') }}</DialogTitle>
width="1000px" </DialogHeader>
append-to-body>
<DataTableLayout <DataTableLayout
class="min-w-0 w-full" class="min-w-0 w-full"
:table="table" :table="table"
:loading="loading" :loading="loading"
:table-style="tableStyle" :table-style="tableStyle"
:page-sizes="pageSizes" :page-sizes="pageSizes"
:total-items="totalItems" :total-items="totalItems"
:on-page-size-change="handlePageSizeChange"> :on-page-size-change="handlePageSizeChange">
<template #toolbar> <template #toolbar>
<div style="display: flex; align-items: center; justify-content: space-between"> <div style="display: flex; align-items: center; justify-content: space-between">
<span style="font-size: 14px" v-text="previousInstancesGroupDialog.groupRef.name"></span> <span style="font-size: 14px" v-text="previousInstancesGroupDialog.groupRef.name"></span>
<InputGroupField <InputGroupField
class="w-1/3" class="w-1/3"
v-model="search" v-model="search"
:placeholder="t('dialog.previous_instances.search_placeholder')" :placeholder="t('dialog.previous_instances.search_placeholder')"
clearable /> clearable />
</div> </div>
</template> </template>
</DataTableLayout> </DataTableLayout>
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { computed, nextTick, ref, watch } from 'vue'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue';
import { InputGroupField } from '@/components/ui/input-group'; import { InputGroupField } from '@/components/ui/input-group';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@@ -44,7 +46,6 @@
import { DataTableLayout } from '../../ui/data-table'; import { DataTableLayout } from '../../ui/data-table';
import { createColumns } from './previousInstancesGroupColumns.jsx'; import { createColumns } from './previousInstancesGroupColumns.jsx';
import { database } from '../../../service/database'; import { database } from '../../../service/database';
import { getNextDialogIndex } from '../../../shared/utils/base/ui';
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable'; import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
const { showPreviousInstancesInfoDialog } = useInstanceStore(); const { showPreviousInstancesInfoDialog } = useInstanceStore();
@@ -52,7 +53,6 @@
const { stringComparer } = storeToRefs(useSearchStore()); const { stringComparer } = storeToRefs(useSearchStore());
const { t } = useI18n(); const { t } = useI18n();
const previousInstancesGroupDialogIndex = ref(2000);
const loading = ref(false); const loading = ref(false);
const modalStore = useModalStore(); const modalStore = useModalStore();
@@ -140,9 +140,6 @@
() => props.previousInstancesGroupDialog.openFlg, () => props.previousInstancesGroupDialog.openFlg,
() => { () => {
if (props.previousInstancesGroupDialog.visible) { if (props.previousInstancesGroupDialog.visible) {
nextTick(() => {
previousInstancesGroupDialogIndex.value = getNextDialogIndex();
});
refreshPreviousInstancesGroupTable(); refreshPreviousInstancesGroupTable();
} }
} }

View File

@@ -1,35 +1,41 @@
<template> <template>
<el-dialog <Dialog
:z-index="previousInstancesInfoDialogIndex" :open="previousInstancesInfoDialogVisible"
:model-value="previousInstancesInfoDialogVisible" @update:open="
:title="t('dialog.previous_instances.info')" (open) => {
width="800px" if (!open) closeDialog();
:fullscreen="fullscreen" }
destroy-on-close ">
@close="closeDialog"> <DialogContent class="sm:max-w-200">
<DataTableLayout <DialogHeader>
class="min-w-0 w-full" <DialogTitle>{{ t('dialog.previous_instances.info') }}</DialogTitle>
:table="table" </DialogHeader>
:loading="loading"
:table-style="tableStyle" <DataTableLayout
:page-sizes="pageSizes" class="min-w-0 w-full"
:total-items="totalItems" :table="table"
:on-page-size-change="handlePageSizeChange"> :loading="loading"
<template #toolbar> :table-style="tableStyle"
<div style="display: flex; align-items: center; justify-content: space-between"> :page-sizes="pageSizes"
<Location :location="location.tag" style="font-size: 14px" /> :total-items="totalItems"
<InputGroupField :on-page-size-change="handlePageSizeChange">
v-model="search" <template #toolbar>
:placeholder="t('dialog.previous_instances.search_placeholder')" <div style="display: flex; align-items: center; justify-content: space-between">
style="width: 150px" <Location :location="location.tag" style="font-size: 14px" />
clearable /> <InputGroupField
</div> v-model="search"
</template> :placeholder="t('dialog.previous_instances.search_placeholder')"
</DataTableLayout> style="width: 150px"
</el-dialog> clearable />
</div>
</template>
</DataTableLayout>
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, nextTick, ref, watch } from 'vue'; import { computed, nextTick, ref, watch } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@@ -40,7 +46,6 @@
import { InputGroupField } from '../../../components/ui/input-group'; import { InputGroupField } from '../../../components/ui/input-group';
import { createColumns } from './previousInstancesInfoColumns.jsx'; import { createColumns } from './previousInstancesInfoColumns.jsx';
import { database } from '../../../service/database'; import { database } from '../../../service/database';
import { getNextDialogIndex } from '../../../shared/utils/base/ui';
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable'; import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
const { lookupUser } = useUserStore(); const { lookupUser } = useUserStore();
@@ -49,8 +54,6 @@
const { gameLogIsFriend, gameLogIsFavorite } = useGameLogStore(); const { gameLogIsFriend, gameLogIsFavorite } = useGameLogStore();
const { t } = useI18n(); const { t } = useI18n();
const previousInstancesInfoDialogIndex = ref(2000);
const loading = ref(false); const loading = ref(false);
const rawRows = ref([]); const rawRows = ref([]);
const search = ref(''); const search = ref('');
@@ -147,7 +150,6 @@
); );
function init() { function init() {
previousInstancesInfoDialogIndex.value = getNextDialogIndex();
loading.value = true; loading.value = true;
location.value = parseLocation(previousInstancesInfoDialogInstanceId.value); location.value = parseLocation(previousInstancesInfoDialogInstanceId.value);
} }

View File

@@ -1,35 +1,37 @@
<template> <template>
<el-dialog <Dialog v-model:open="isVisible">
:z-index="previousInstancesWorldDialogIndex" <DialogContent class="sm:max-w-250">
v-model="isVisible" <DialogHeader>
:title="t('dialog.previous_instances.header')" <DialogTitle>{{ t('dialog.previous_instances.header') }}</DialogTitle>
width="1000px" </DialogHeader>
append-to-body>
<DataTableLayout <DataTableLayout
class="min-w-0 w-full" class="min-w-0 w-full"
:table="table" :table="table"
:loading="loading" :loading="loading"
:table-style="tableStyle" :table-style="tableStyle"
:page-sizes="pageSizes" :page-sizes="pageSizes"
:total-items="totalItems" :total-items="totalItems"
:on-page-size-change="handlePageSizeChange"> :on-page-size-change="handlePageSizeChange">
<template #toolbar> <template #toolbar>
<div style="display: flex; align-items: center; justify-content: space-between"> <div style="display: flex; align-items: center; justify-content: space-between">
<span style="font-size: 14px" v-text="previousInstancesWorldDialog.worldRef.name"></span> <span style="font-size: 14px" v-text="previousInstancesWorldDialog.worldRef.name"></span>
<InputGroupField <InputGroupField
v-model="search" v-model="search"
:placeholder="t('dialog.previous_instances.search_placeholder')" :placeholder="t('dialog.previous_instances.search_placeholder')"
clearable clearable
class="w-1/3" class="w-1/3"
style="display: block" /> style="display: block" />
</div> </div>
</template> </template>
</DataTableLayout> </DataTableLayout>
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { computed, nextTick, ref, watch } from 'vue'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue';
import { InputGroupField } from '@/components/ui/input-group'; import { InputGroupField } from '@/components/ui/input-group';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@@ -52,7 +54,6 @@
import { DataTableLayout } from '../../ui/data-table'; import { DataTableLayout } from '../../ui/data-table';
import { createColumns } from './previousInstancesWorldColumns.jsx'; import { createColumns } from './previousInstancesWorldColumns.jsx';
import { database } from '../../../service/database'; import { database } from '../../../service/database';
import { getNextDialogIndex } from '../../../shared/utils/base/ui';
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable'; import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
const { t } = useI18n(); const { t } = useI18n();
@@ -79,7 +80,6 @@
const pageSize = ref(10); const pageSize = ref(10);
const tableStyle = { maxHeight: '400px' }; const tableStyle = { maxHeight: '400px' };
const loading = ref(false); const loading = ref(false);
const previousInstancesWorldDialogIndex = ref(2000);
const isVisible = computed({ const isVisible = computed({
get: () => props.previousInstancesWorldDialog.visible, get: () => props.previousInstancesWorldDialog.visible,
@@ -188,9 +188,6 @@
() => props.previousInstancesWorldDialog.openFlg, () => props.previousInstancesWorldDialog.openFlg,
() => { () => {
if (props.previousInstancesWorldDialog.visible) { if (props.previousInstancesWorldDialog.visible) {
nextTick(() => {
previousInstancesWorldDialogIndex.value = getNextDialogIndex();
});
refreshPreviousInstancesWorldTable(); refreshPreviousInstancesWorldTable();
} }
} }

View File

@@ -1,10 +1,9 @@
<template> <template>
<el-dialog <Dialog v-model:open="sendBoopDialog.visible">
class="x-dialog" <DialogContent>
v-model="sendBoopDialog.visible" <DialogHeader>
:title="t('dialog.boop_dialog.header')" <DialogTitle>{{ t('dialog.boop_dialog.header') }}</DialogTitle>
width="450px" </DialogHeader>
@close="closeDialog">
<span>{{ displayName }}</span> <span>{{ displayName }}</span>
<br /> <br />
@@ -58,7 +57,7 @@
</div> </div>
</div> </div>
<template #footer> <DialogFooter>
<Button size="sm" variant="outline" class="mr-2" @click="showGalleryPage">{{ <Button size="sm" variant="outline" class="mr-2" @click="showGalleryPage">{{
t('dialog.boop_dialog.emoji_manager') t('dialog.boop_dialog.emoji_manager')
}}</Button> }}</Button>
@@ -68,14 +67,16 @@
<Button size="sm" :disabled="!sendBoopDialog.userId" @click="sendBoop">{{ <Button size="sm" :disabled="!sendBoopDialog.userId" @click="sendBoop">{{
t('dialog.boop_dialog.send') t('dialog.boop_dialog.send')
}}</Button> }}</Button>
</template> </DialogFooter>
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Check as CheckIcon } from 'lucide-vue-next'; import { Check as CheckIcon } from 'lucide-vue-next';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';

View File

@@ -1,56 +1,58 @@
<template> <template>
<el-dialog <Dialog v-model:open="bioDialog.visible">
class="x-dialog" <DialogContent class="x-dialog sm:max-w-150">
v-model="bioDialog.visible" <DialogHeader>
:title="t('dialog.bio.header')" <DialogTitle>{{ t('dialog.bio.header') }}</DialogTitle>
width="600px" </DialogHeader>
append-to-body>
<div v-loading="bioDialog.loading">
<InputGroupTextareaField
v-model="bioDialog.bio"
:maxlength="512"
:rows="5"
:placeholder="t('dialog.bio.bio_placeholder')"
class="mb-2.5"
show-count />
<InputGroupAction <div v-loading="bioDialog.loading">
v-for="(link, index) in bioDialog.bioLinks" <InputGroupTextareaField
:key="index" v-model="bioDialog.bio"
v-model="bioDialog.bioLinks[index]" :maxlength="512"
:maxlength="64" :rows="5"
show-count :placeholder="t('dialog.bio.bio_placeholder')"
size="sm" class="mb-2.5"
style="margin-top: 5px"> show-count />
<template #leading>
<img :src="getFaviconUrl(link)" style="width: 16px; height: 16px; vertical-align: middle" />
</template>
<template #actions>
<Button variant="ghost" size="icon-sm" @click="bioDialog.bioLinks.splice(index, 1)"
><Trash2 class="size-4"
/></Button>
</template>
</InputGroupAction>
<Button <InputGroupAction
variant="outline" v-for="(link, index) in bioDialog.bioLinks"
:disabled="bioDialog.bioLinks.length >= 3" :key="index"
size="sm" v-model="bioDialog.bioLinks[index]"
class="mt-2" :maxlength="64"
@click="bioDialog.bioLinks.push('')"> show-count
{{ t('dialog.bio.add_link') }} size="sm"
</Button> style="margin-top: 5px">
</div> <template #leading>
<img :src="getFaviconUrl(link)" style="width: 16px; height: 16px; vertical-align: middle" />
</template>
<template #actions>
<Button variant="ghost" size="icon-sm" @click="bioDialog.bioLinks.splice(index, 1)"
><Trash2 class="size-4"
/></Button>
</template>
</InputGroupAction>
<template #footer> <Button
<Button :disabled="bioDialog.loading" @click="saveBio"> variant="outline"
{{ t('dialog.bio.update') }} :disabled="bioDialog.bioLinks.length >= 3"
</Button> size="sm"
</template> class="mt-2"
</el-dialog> @click="bioDialog.bioLinks.push('')">
{{ t('dialog.bio.add_link') }}
</Button>
</div>
<DialogFooter>
<Button :disabled="bioDialog.loading" @click="saveBio">
{{ t('dialog.bio.update') }}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { InputGroupAction, InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupAction, InputGroupTextareaField } from '@/components/ui/input-group';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Trash2 } from 'lucide-vue-next'; import { Trash2 } from 'lucide-vue-next';

View File

@@ -1,45 +1,49 @@
<template> <template>
<el-dialog <Dialog
class="x-dialog" :open="props.visible"
:model-value="props.visible" @update:open="
title="Edit Note And Memo" (open) => {
:show-close="false" if (!open) cancel();
top="30vh" }
width="500px" ">
append-to-body <DialogContent class="x-dialog sm:max-w-125 translate-y-0" style="top: 30vh" :show-close-button="false">
@close="cancel"> <DialogHeader>
<template v-if="!hideUserNotes || (hideUserNotes && hideUserMemos)"> <DialogTitle>Edit Note And Memo</DialogTitle>
<span class="name my-2">{{ t('dialog.user.info.note') }}</span> </DialogHeader>
<br />
<InputGroupTextareaField <template v-if="!hideUserNotes || (hideUserNotes && hideUserMemos)">
v-model="note" <span class="name my-2">{{ t('dialog.user.info.note') }}</span>
:autosize="{ minRows: 6, maxRows: 20 }" <br />
:maxlength="256" <InputGroupTextareaField
:rows="6" v-model="note"
:placeholder="t('dialog.user.info.note_placeholder')" :autosize="{ minRows: 6, maxRows: 20 }"
input-class="extra resize-none" :maxlength="256"
class="my-2" :rows="6"
show-count /> :placeholder="t('dialog.user.info.note_placeholder')"
</template> input-class="extra resize-none"
<template v-if="!hideUserMemos || (hideUserNotes && hideUserMemos)"> class="my-2"
<span class="name">{{ t('dialog.user.info.memo') }}</span> show-count />
<InputGroupTextareaField </template>
v-model="memo" <template v-if="!hideUserMemos || (hideUserNotes && hideUserMemos)">
class="extra mt-2" <span class="name">{{ t('dialog.user.info.memo') }}</span>
:rows="6" <InputGroupTextareaField
:placeholder="t('dialog.user.info.memo_placeholder')" v-model="memo"
input-class="resize-none min-h-0" /> class="extra mt-2"
</template> :rows="6"
<template #footer> :placeholder="t('dialog.user.info.memo_placeholder')"
<div class="dialog-footer"> input-class="resize-none min-h-0" />
</template>
<DialogFooter>
<Button variant="secondary" @click="cancel" class="mr-2">Cancel</Button> <Button variant="secondary" @click="cancel" class="mr-2">Cancel</Button>
<Button @click="saveChanges">Confirm</Button> <Button @click="saveChanges">Confirm</Button>
</div> </DialogFooter>
</template> </DialogContent>
</el-dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';

View File

@@ -1,63 +1,67 @@
<template> <template>
<el-dialog <Dialog v-model:open="languageDialog.visible">
class="x-dialog" <DialogContent class="x-dialog sm:max-w-100">
v-model="languageDialog.visible" <DialogHeader>
:title="t('dialog.language.header')" <DialogTitle>{{ t('dialog.language.header') }}</DialogTitle>
width="400px" </DialogHeader>
append-to-body>
<div v-loading="languageDialog.loading"> <div v-loading="languageDialog.loading">
<div v-for="item in currentUser.$languages" :key="item.key" style="margin: 6px 0"> <div v-for="item in currentUser.$languages" :key="item.key" style="margin: 6px 0">
<Badge variant="outline" style="margin-right: 5px"> <Badge variant="outline" style="margin-right: 5px">
<span <span
class="flags" class="flags"
:class="languageClass(item.key)" :class="languageClass(item.key)"
style="display: inline-block; margin-right: 5px"></span> style="display: inline-block; margin-right: 5px"></span>
{{ item.value }} ({{ item.key.toUpperCase() }}) {{ item.value }} ({{ item.key.toUpperCase() }})
<button <button
type="button" type="button"
style=" style="
margin-left: 6px; margin-left: 6px;
border: none; border: none;
background: transparent; background: transparent;
padding: 0; padding: 0;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
color: inherit; color: inherit;
cursor: pointer; cursor: pointer;
" "
@click="removeUserLanguage(item.key)"> @click="removeUserLanguage(item.key)">
<X class="h-3 w-3" /> <X class="h-3 w-3" />
</button> </button>
</Badge> </Badge>
</div>
<Select
:model-value="selectedLanguageToAdd"
:disabled="
languageDialog.loading || (currentUser.$languages && currentUser.$languages.length === 3)
"
@update:modelValue="handleAddUserLanguage">
<SelectTrigger size="sm" style="margin-top: 14px">
<SelectValue :placeholder="t('dialog.language.select_language')" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem
v-for="item in languageDialog.languages"
:key="item.key"
:value="item.key"
:text-value="item.value">
<span
class="flags"
:class="languageClass(item.key)"
style="display: inline-block; margin-right: 5px"></span>
{{ item.value }} ({{ item.key.toUpperCase() }})
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div> </div>
<Select </DialogContent>
:model-value="selectedLanguageToAdd" </Dialog>
:disabled="languageDialog.loading || (currentUser.$languages && currentUser.$languages.length === 3)"
@update:modelValue="handleAddUserLanguage">
<SelectTrigger size="sm" style="margin-top: 14px">
<SelectValue :placeholder="t('dialog.language.select_language')" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem
v-for="item in languageDialog.languages"
:key="item.key"
:value="item.key"
:text-value="item.value">
<span
class="flags"
:class="languageClass(item.key)"
style="display: inline-block; margin-right: 5px"></span>
{{ item.value }} ({{ item.key.toUpperCase() }})
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</el-dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { X } from 'lucide-vue-next'; import { X } from 'lucide-vue-next';
import { ref } from 'vue'; import { ref } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';

View File

@@ -1,35 +1,37 @@
<template> <template>
<el-dialog <Dialog v-model:open="isVisible">
:z-index="previousInstancesUserDialogIndex" <DialogContent class="sm:max-w-250">
v-model="isVisible" <DialogHeader>
:title="t('dialog.previous_instances.header')" <DialogTitle>{{ t('dialog.previous_instances.header') }}</DialogTitle>
width="1000px" </DialogHeader>
append-to-body>
<DataTableLayout <DataTableLayout
class="min-w-0 w-full" class="min-w-0 w-full"
:table="table" :table="table"
:loading="loading" :loading="loading"
:table-style="tableStyle" :table-style="tableStyle"
:page-sizes="pageSizes" :page-sizes="pageSizes"
:total-items="totalItems" :total-items="totalItems"
:on-page-size-change="handlePageSizeChange"> :on-page-size-change="handlePageSizeChange">
<template #toolbar> <template #toolbar>
<div style="display: flex; align-items: center; justify-content: space-between"> <div style="display: flex; align-items: center; justify-content: space-between">
<span style="font-size: 14px" v-text="previousInstancesUserDialog.userRef.displayName"></span> <span style="font-size: 14px" v-text="previousInstancesUserDialog.userRef.displayName"></span>
<InputGroupField <InputGroupField
v-model="search" v-model="search"
:placeholder="t('dialog.previous_instances.search_placeholder')" :placeholder="t('dialog.previous_instances.search_placeholder')"
clearable clearable
class="w-1/3" class="w-1/3"
style="display: block" /> style="display: block" />
</div> </div>
</template> </template>
</DataTableLayout> </DataTableLayout>
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { computed, nextTick, ref, watch } from 'vue'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue';
import { InputGroupField } from '@/components/ui/input-group'; import { InputGroupField } from '@/components/ui/input-group';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@@ -52,7 +54,6 @@
import { DataTableLayout } from '../../ui/data-table'; import { DataTableLayout } from '../../ui/data-table';
import { createColumns } from './previousInstancesUserColumns.jsx'; import { createColumns } from './previousInstancesUserColumns.jsx';
import { database } from '../../../service/database'; import { database } from '../../../service/database';
import { getNextDialogIndex } from '../../../shared/utils/base/ui';
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable'; import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
const props = defineProps({ const props = defineProps({
@@ -90,8 +91,6 @@
const vrcxStore = useVrcxStore(); const vrcxStore = useVrcxStore();
const { t } = useI18n(); const { t } = useI18n();
const previousInstancesUserDialogIndex = ref(2000);
const isVisible = computed({ const isVisible = computed({
get: () => props.previousInstancesUserDialog.visible, get: () => props.previousInstancesUserDialog.visible,
set: (value) => { set: (value) => {
@@ -178,9 +177,6 @@
() => props.previousInstancesUserDialog.openFlg, () => props.previousInstancesUserDialog.openFlg,
() => { () => {
if (props.previousInstancesUserDialog.visible) { if (props.previousInstancesUserDialog.visible) {
nextTick(() => {
previousInstancesUserDialogIndex.value = getNextDialogIndex();
});
refreshPreviousInstancesUserTable(); refreshPreviousInstancesUserTable();
} }
} }

View File

@@ -1,27 +1,30 @@
<template> <template>
<el-dialog <Dialog v-model:open="pronounsDialog.visible">
class="x-dialog" <DialogContent class="x-dialog sm:max-w-150">
v-model="pronounsDialog.visible" <DialogHeader>
:title="t('dialog.pronouns.header')" <DialogTitle>{{ t('dialog.pronouns.header') }}</DialogTitle>
width="600px" </DialogHeader>
append-to-body>
<div v-loading="pronounsDialog.loading"> <div v-loading="pronounsDialog.loading">
<InputGroupTextareaField <InputGroupTextareaField
v-model="pronounsDialog.pronouns" v-model="pronounsDialog.pronouns"
:maxlength="32" :maxlength="32"
:rows="2" :rows="2"
:placeholder="t('dialog.pronouns.pronouns_placeholder')" :placeholder="t('dialog.pronouns.pronouns_placeholder')"
show-count /> show-count />
</div> </div>
<template #footer>
<Button :disabled="pronounsDialog.loading" @click="savePronouns"> <DialogFooter>
{{ t('dialog.pronouns.update') }} <Button :disabled="pronounsDialog.loading" @click="savePronouns">
</Button> {{ t('dialog.pronouns.update') }}
</template> </Button>
</el-dialog> </DialogFooter>
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';

View File

@@ -1,30 +1,37 @@
<template> <template>
<el-dialog <Dialog
class="x-dialog" :open="sendInviteRequestDialogVisible"
:model-value="sendInviteRequestDialogVisible" @update:open="
:title="t('dialog.invite_request_message.header')" (open) => {
width="800px" if (!open) cancelSendInviteRequest();
append-to-body }
@close="cancelSendInviteRequest"> ">
<template v-if="isLocalUserVrcPlusSupporter"> <DialogContent class="x-dialog sm:max-w-200">
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" /> <DialogHeader>
</template> <DialogTitle>{{ t('dialog.invite_request_message.header') }}</DialogTitle>
</DialogHeader>
<DataTableLayout <template v-if="isLocalUserVrcPlusSupporter">
style="margin-top: 10px" <input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
:table="inviteRequestMessageTanstackTable" </template>
:loading="false"
:show-pagination="false" <DataTableLayout
:on-row-click="handleInviteRequestMessageRowClick" /> style="margin-top: 10px"
:table="inviteRequestMessageTanstackTable"
:loading="false"
:show-pagination="false"
:on-row-click="handleInviteRequestMessageRowClick" />
<DialogFooter>
<Button variant="secondary" class="mr-2" @click="cancelSendInviteRequest">{{
t('dialog.invite_request_message.cancel')
}}</Button>
<Button @click="refreshInviteMessageTableData('request')">{{
t('dialog.invite_request_message.refresh')
}}</Button>
</DialogFooter>
</DialogContent>
<template #footer>
<Button variant="secondary" class="mr-2" @click="cancelSendInviteRequest">{{
t('dialog.invite_request_message.cancel')
}}</Button>
<Button @click="refreshInviteMessageTableData('request')">{{
t('dialog.invite_request_message.refresh')
}}</Button>
</template>
<SendInviteConfirmDialog <SendInviteConfirmDialog
v-model:isSendInviteConfirmDialogVisible="isSendInviteConfirmDialogVisible" v-model:isSendInviteConfirmDialogVisible="isSendInviteConfirmDialogVisible"
:sendInviteDialog="sendInviteDialog" :sendInviteDialog="sendInviteDialog"
@@ -37,10 +44,11 @@
@update:sendInviteDialog="emit('update:sendInviteDialog', $event)" @update:sendInviteDialog="emit('update:sendInviteDialog', $event)"
:invite-dialog="inviteDialog" :invite-dialog="inviteDialog"
@closeInviteDialog="closeInviteDialog" /> @closeInviteDialog="closeInviteDialog" />
</el-dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { DataTableLayout } from '@/components/ui/data-table'; import { DataTableLayout } from '@/components/ui/data-table';

View File

@@ -1,96 +1,98 @@
<template> <template>
<el-dialog <Dialog v-model:open="socialStatusDialog.visible">
class="x-dialog" <DialogContent class="x-dialog sm:max-w-100">
v-model="socialStatusDialog.visible" <DialogHeader>
:title="t('dialog.social_status.header')" <DialogTitle>{{ t('dialog.social_status.header') }}</DialogTitle>
append-to-body </DialogHeader>
width="400px">
<div v-loading="socialStatusDialog.loading">
<Select :model-value="socialStatusDialog.status" @update:modelValue="handleSocialStatusChange">
<SelectTrigger size="sm" style="margin-top: 10px; width: 100%">
<span class="flex items-center gap-2">
<i v-if="socialStatusDialog.status === 'join me'" class="x-user-status joinme"></i>
<i v-else-if="socialStatusDialog.status === 'active'" class="x-user-status online"></i>
<i v-else-if="socialStatusDialog.status === 'ask me'" class="x-user-status askme"></i>
<i v-else-if="socialStatusDialog.status === 'busy'" class="x-user-status busy"></i>
<i v-else-if="socialStatusDialog.status === 'offline'" class="x-user-status offline"></i>
<SelectValue :placeholder="t('dialog.social_status.status_placeholder')" />
</span>
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="join me" :text-value="t('dialog.user.status.join_me')">
<i class="x-user-status joinme"></i> {{ t('dialog.user.status.join_me') }}
</SelectItem>
<SelectItem value="active" :text-value="t('dialog.user.status.online')">
<i class="x-user-status online"></i> {{ t('dialog.user.status.online') }}
</SelectItem>
<SelectItem value="ask me" :text-value="t('dialog.user.status.ask_me')">
<i class="x-user-status askme"></i> {{ t('dialog.user.status.ask_me') }}
</SelectItem>
<SelectItem value="busy" :text-value="t('dialog.user.status.busy')">
<i class="x-user-status busy"></i> {{ t('dialog.user.status.busy') }}
</SelectItem>
<SelectItem
v-if="currentUser.$isModerator"
value="offline"
:text-value="t('dialog.user.status.offline')">
<i class="x-user-status offline"></i> {{ t('dialog.user.status.offline') }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<InputGroupField <div v-loading="socialStatusDialog.loading">
v-model="socialStatusDialog.statusDescription" <Select :model-value="socialStatusDialog.status" @update:modelValue="handleSocialStatusChange">
:placeholder="t('dialog.social_status.status_placeholder')" <SelectTrigger size="sm" style="margin-top: 10px; width: 100%">
:maxlength="32" <span class="flex items-center gap-2">
clearable <i v-if="socialStatusDialog.status === 'join me'" class="x-user-status joinme"></i>
show-count <i v-else-if="socialStatusDialog.status === 'active'" class="x-user-status online"></i>
class="mt-2.5" /> <i v-else-if="socialStatusDialog.status === 'ask me'" class="x-user-status askme"></i>
<Collapsible v-model:open="isOpen" class="mt-3 flex w-full flex-col gap-2"> <i v-else-if="socialStatusDialog.status === 'busy'" class="x-user-status busy"></i>
<div class="flex items-center justify-between gap-4 px-4"> <i v-else-if="socialStatusDialog.status === 'offline'" class="x-user-status offline"></i>
<h4 class="text-sm font-semibold">{{ t('dialog.social_status.history') }}</h4> <SelectValue :placeholder="t('dialog.social_status.status_placeholder')" />
<CollapsibleTrigger as-child> </span>
<Button variant="ghost" size="icon" class="size-8"> </SelectTrigger>
<ChevronsUpDown /> <SelectContent>
<span class="sr-only">Toggle</span> <SelectGroup>
</Button> <SelectItem value="join me" :text-value="t('dialog.user.status.join_me')">
</CollapsibleTrigger> <i class="x-user-status joinme"></i> {{ t('dialog.user.status.join_me') }}
</div> </SelectItem>
<div <SelectItem value="active" :text-value="t('dialog.user.status.online')">
v-if="!isOpen && latestHistoryItem" <i class="x-user-status online"></i> {{ t('dialog.user.status.online') }}
class="cursor-pointer rounded-md border w-full px-4 py-2 font-mono text-sm" </SelectItem>
@click="setSocialStatusFromHistory(latestHistoryItem)"> <SelectItem value="ask me" :text-value="t('dialog.user.status.ask_me')">
{{ latestHistoryItem.status }} <i class="x-user-status askme"></i> {{ t('dialog.user.status.ask_me') }}
</div> </SelectItem>
<CollapsibleContent class="flex flex-col gap-2"> <SelectItem value="busy" :text-value="t('dialog.user.status.busy')">
<div <i class="x-user-status busy"></i> {{ t('dialog.user.status.busy') }}
v-for="item in historyItems" </SelectItem>
:key="item.no ?? item.status" <SelectItem
class="cursor-pointer rounded-md border w-full px-4 py-2 font-mono text-sm" v-if="currentUser.$isModerator"
@click="setSocialStatusFromHistory(item)"> value="offline"
{{ item.status }} :text-value="t('dialog.user.status.offline')">
<i class="x-user-status offline"></i> {{ t('dialog.user.status.offline') }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<InputGroupField
v-model="socialStatusDialog.statusDescription"
:placeholder="t('dialog.social_status.status_placeholder')"
:maxlength="32"
clearable
show-count
class="mt-2.5" />
<Collapsible v-model:open="isOpen" class="mt-3 flex w-full flex-col gap-2">
<div class="flex items-center justify-between gap-4 px-4">
<h4 class="text-sm font-semibold">{{ t('dialog.social_status.history') }}</h4>
<CollapsibleTrigger as-child>
<Button variant="ghost" size="icon" class="size-8">
<ChevronsUpDown />
<span class="sr-only">Toggle</span>
</Button>
</CollapsibleTrigger>
</div> </div>
</CollapsibleContent> <div
</Collapsible> v-if="!isOpen && latestHistoryItem"
</div> class="cursor-pointer rounded-md border w-full px-4 py-2 font-mono text-sm"
@click="setSocialStatusFromHistory(latestHistoryItem)">
{{ latestHistoryItem.status }}
</div>
<CollapsibleContent class="flex flex-col gap-2">
<div
v-for="item in historyItems"
:key="item.no ?? item.status"
class="cursor-pointer rounded-md border w-full px-4 py-2 font-mono text-sm"
@click="setSocialStatusFromHistory(item)">
{{ item.status }}
</div>
</CollapsibleContent></Collapsible
>
</div>
<template #footer> <DialogFooter>
<Button :disabled="socialStatusDialog.loading" @click="saveSocialStatus"> <Button :disabled="socialStatusDialog.loading" @click="saveSocialStatus">
{{ t('dialog.social_status.update') }} {{ t('dialog.social_status.update') }}
</Button> </Button>
</template> </DialogFooter>
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { InputGroupField } from '@/components/ui/input-group';
import { ChevronsUpDown } from 'lucide-vue-next'; import { ChevronsUpDown } from 'lucide-vue-next';
import { InputGroupField } from '@/components/ui/input-group';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,10 @@
<template> <template>
<el-dialog <Dialog v-model:open="VRCXUpdateDialog.visible">
:z-index="VRCXUpdateDialogIndex" <DialogContent>
class="x-dialog" <DialogHeader>
v-model="VRCXUpdateDialog.visible" <DialogTitle>{{ t('dialog.vrcx_updater.header') }}</DialogTitle>
:title="t('dialog.vrcx_updater.header')" </DialogHeader>
append-to-body <div v-loading="checkingForVRCXUpdate" style="margin-top: 15px">
width="400px">
<div v-loading="checkingForVRCXUpdate" style="margin-top: 15px">
<template v-if="updateInProgress"> <template v-if="updateInProgress">
<Progress :model-value="updateProgress" class="w-full" /> <Progress :model-value="updateProgress" class="w-full" />
<div class="mt-2 text-xs" v-text="updateProgressText()"></div> <div class="mt-2 text-xs" v-text="updateProgressText()"></div>
@@ -61,9 +59,9 @@
<span>{{ t('dialog.vrcx_updater.latest_version') }}</span> <span>{{ t('dialog.vrcx_updater.latest_version') }}</span>
</div> </div>
</template> </template>
</div> </div>
<template #footer> <DialogFooter>
<Button variant="secondary" class="mr-2" v-if="updateInProgress" @click="cancelUpdate"> <Button variant="secondary" class="mr-2" v-if="updateInProgress" @click="cancelUpdate">
{{ t('dialog.vrcx_updater.cancel') }} {{ t('dialog.vrcx_updater.cancel') }}
</Button> </Button>
@@ -77,15 +75,16 @@
<Button variant="default" v-if="!updateInProgress && pendingVRCXInstall" @click="restartVRCX(true)"> <Button variant="default" v-if="!updateInProgress && pendingVRCXInstall" @click="restartVRCX(true)">
{{ t('dialog.vrcx_updater.install') }} {{ t('dialog.vrcx_updater.install') }}
</Button> </Button>
</template> </DialogFooter>
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field'; import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { nextTick, ref, watch } from 'vue'; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { AlertCircle } from 'lucide-vue-next'; import { AlertCircle } from 'lucide-vue-next';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Progress } from '@/components/ui/progress'; import { Progress } from '@/components/ui/progress';
@@ -93,7 +92,6 @@
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
import { getNextDialogIndex } from '../../shared/utils/base/ui';
import { useVRCXUpdaterStore } from '../../stores'; import { useVRCXUpdaterStore } from '../../stores';
const VRCXUpdaterStore = useVRCXUpdaterStore(); const VRCXUpdaterStore = useVRCXUpdaterStore();
@@ -111,7 +109,6 @@
const { t } = useI18n(); const { t } = useI18n();
const VRCXUpdateDialogIndex = ref(2000);
const handleBranchChange = (value) => { const handleBranchChange = (value) => {
if (!value || value === branch.value) { if (!value || value === branch.value) {
return; return;
@@ -119,15 +116,4 @@
branch.value = value; branch.value = value;
loadBranchVersions(); loadBranchVersions();
}; };
watch(
() => VRCXUpdateDialog,
(newVal) => {
if (newVal.value.visible) {
nextTick(() => {
VRCXUpdateDialogIndex.value = getNextDialogIndex();
});
}
}
);
</script> </script>

View File

@@ -1,33 +1,40 @@
<template> <template>
<el-dialog <Dialog
class="x-dialog" :open="changeWorldImageDialogVisible"
:model-value="changeWorldImageDialogVisible" @update:open="
:title="t('dialog.change_content_image.world')" (open) => {
width="850px" if (!open) closeDialog();
append-to-body }
@close="closeDialog"> ">
<div> <DialogContent class="x-dialog sm:max-w-212.5">
<input <DialogHeader>
id="WorldImageUploadButton" <DialogTitle>{{ t('dialog.change_content_image.world') }}</DialogTitle>
type="file" </DialogHeader>
accept="image/*"
style="display: none" <div>
@change="onFileChangeWorldImage" /> <input
<span>{{ t('dialog.change_content_image.description') }}</span> id="WorldImageUploadButton"
<br /> type="file"
<Button variant="outline" size="sm" :disabled="changeWorldImageDialogLoading" @click="uploadWorldImage"> accept="image/*"
<Upload /> style="display: none"
{{ t('dialog.change_content_image.upload') }} @change="onFileChangeWorldImage" />
</Button> <span>{{ t('dialog.change_content_image.description') }}</span>
<br /> <br />
<div class="x-change-image-item"> <Button variant="outline" size="sm" :disabled="changeWorldImageDialogLoading" @click="uploadWorldImage">
<img :src="previousImageUrl" class="img-size" loading="lazy" /> <Upload />
{{ t('dialog.change_content_image.upload') }}
</Button>
<br />
<div class="x-change-image-item">
<img :src="previousImageUrl" class="img-size" loading="lazy" />
</div>
</div> </div>
</div> </DialogContent>
</el-dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Upload } from 'lucide-vue-next'; import { Upload } from 'lucide-vue-next';
import { ref } from 'vue'; import { ref } from 'vue';

View File

@@ -1,107 +1,110 @@
<template> <template>
<el-dialog <Dialog v-model:open="isVisible">
v-model="isVisible" <DialogContent class="sm:max-w-100">
:title="t('dialog.set_world_tags.header')" <DialogHeader>
width="400px" <DialogTitle>{{ t('dialog.set_world_tags.header') }}</DialogTitle>
destroy-on-close </DialogHeader>
append-to-body>
<label class="inline-flex items-center gap-2"> <label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.avatarScalingDisabled" /> <Checkbox v-model="setWorldTagsDialog.avatarScalingDisabled" />
<span>{{ t('dialog.set_world_tags.avatar_scaling_disabled') }}</span> <span>{{ t('dialog.set_world_tags.avatar_scaling_disabled') }}</span>
</label> </label>
<br /> <br />
<label class="inline-flex items-center gap-2"> <label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.focusViewDisabled" /> <Checkbox v-model="setWorldTagsDialog.focusViewDisabled" />
<span>{{ t('dialog.set_world_tags.focus_view_disabled') }}</span> <span>{{ t('dialog.set_world_tags.focus_view_disabled') }}</span>
</label> </label>
<br /> <br />
<label class="inline-flex items-center gap-2"> <label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.debugAllowed" /> <Checkbox v-model="setWorldTagsDialog.debugAllowed" />
<span>{{ t('dialog.set_world_tags.enable_debugging') }}</span> <span>{{ t('dialog.set_world_tags.enable_debugging') }}</span>
</label> </label>
<div style="font-size: 12px; margin-top: 10px">{{ t('dialog.set_world_tags.author_tags') }}<br /></div> <div style="font-size: 12px; margin-top: 10px">{{ t('dialog.set_world_tags.author_tags') }}<br /></div>
<InputGroupTextareaField <InputGroupTextareaField
v-model="setWorldTagsDialog.authorTags" v-model="setWorldTagsDialog.authorTags"
:rows="2" :rows="2"
placeholder="" placeholder=""
style="margin-top: 10px" style="margin-top: 10px"
input-class="resize-none" /> input-class="resize-none" />
<div style="font-size: 12px; margin-top: 10px">{{ t('dialog.set_world_tags.content_tags') }}<br /></div> <div style="font-size: 12px; margin-top: 10px">{{ t('dialog.set_world_tags.content_tags') }}<br /></div>
<label class="inline-flex items-center gap-2"> <label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.contentHorror" /> <Checkbox v-model="setWorldTagsDialog.contentHorror" />
<span>{{ t('dialog.set_world_tags.content_horror') }}</span> <span>{{ t('dialog.set_world_tags.content_horror') }}</span>
</label> </label>
<br /> <br />
<label class="inline-flex items-center gap-2"> <label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.contentGore" /> <Checkbox v-model="setWorldTagsDialog.contentGore" />
<span>{{ t('dialog.set_world_tags.content_gore') }}</span> <span>{{ t('dialog.set_world_tags.content_gore') }}</span>
</label> </label>
<br /> <br />
<label class="inline-flex items-center gap-2"> <label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.contentViolence" /> <Checkbox v-model="setWorldTagsDialog.contentViolence" />
<span>{{ t('dialog.set_world_tags.content_violence') }}</span> <span>{{ t('dialog.set_world_tags.content_violence') }}</span>
</label> </label>
<br /> <br />
<label class="inline-flex items-center gap-2"> <label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.contentAdult" /> <Checkbox v-model="setWorldTagsDialog.contentAdult" />
<span>{{ t('dialog.set_world_tags.content_adult') }}</span> <span>{{ t('dialog.set_world_tags.content_adult') }}</span>
</label> </label>
<br /> <br />
<label class="inline-flex items-center gap-2"> <label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.contentSex" /> <Checkbox v-model="setWorldTagsDialog.contentSex" />
<span>{{ t('dialog.set_world_tags.content_sex') }}</span> <span>{{ t('dialog.set_world_tags.content_sex') }}</span>
</label> </label>
<div style="font-size: 12px; margin-top: 10px"> <div style="font-size: 12px; margin-top: 10px">
{{ t('dialog.set_world_tags.default_content_settings') }}<br /> {{ t('dialog.set_world_tags.default_content_settings') }}<br />
</div>
<label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.emoji" />
<span>{{ t('dialog.new_instance.content_emoji') }}</span>
</label>
<br />
<label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.stickers" />
<span>{{ t('dialog.new_instance.content_stickers') }}</span>
</label>
<br />
<label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.pedestals" />
<span>{{ t('dialog.new_instance.content_pedestals') }}</span>
</label>
<br />
<label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.prints" />
<span>{{ t('dialog.new_instance.content_prints') }}</span>
</label>
<br />
<label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.drones" />
<span>{{ t('dialog.new_instance.content_drones') }}</span>
</label>
<br />
<label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.props" />
<span>{{ t('dialog.new_instance.content_items') }}</span>
</label>
<br />
<label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.thirdPerson" />
<span>{{ t('dialog.new_instance.content_third_person') }}</span>
</label>
<template #footer>
<div class="flex gap-2">
<Button variant="secondary" @click="isVisible = false">
{{ t('dialog.set_world_tags.cancel') }}
</Button>
<Button @click="saveSetWorldTagsDialog">
{{ t('dialog.set_world_tags.save') }}
</Button>
</div> </div>
</template> <label class="inline-flex items-center gap-2">
</el-dialog> <Checkbox v-model="setWorldTagsDialog.emoji" />
<span>{{ t('dialog.new_instance.content_emoji') }}</span>
</label>
<br />
<label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.stickers" />
<span>{{ t('dialog.new_instance.content_stickers') }}</span>
</label>
<br />
<label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.pedestals" />
<span>{{ t('dialog.new_instance.content_pedestals') }}</span>
</label>
<br />
<label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.prints" />
<span>{{ t('dialog.new_instance.content_prints') }}</span>
</label>
<br />
<label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.drones" />
<span>{{ t('dialog.new_instance.content_drones') }}</span>
</label>
<br />
<label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.props" />
<span>{{ t('dialog.new_instance.content_items') }}</span>
</label>
<br />
<label class="inline-flex items-center gap-2">
<Checkbox v-model="setWorldTagsDialog.thirdPerson" />
<span>{{ t('dialog.new_instance.content_third_person') }}</span>
</label>
<DialogFooter>
<div class="flex gap-2">
<Button variant="secondary" @click="isVisible = false">
{{ t('dialog.set_world_tags.cancel') }}
</Button>
<Button @click="saveSetWorldTagsDialog">
{{ t('dialog.set_world_tags.save') }}
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';

View File

@@ -1,34 +1,37 @@
<template> <template>
<el-dialog <Dialog v-model:open="isVisible">
v-model="isVisible" <DialogContent class="sm:max-w-150">
:title="t('dialog.allowed_video_player_domains.header')" <DialogHeader>
width="600px" <DialogTitle>{{ t('dialog.allowed_video_player_domains.header') }}</DialogTitle>
destroy-on-close </DialogHeader>
append-to-body>
<div> <div>
<InputGroupAction <InputGroupAction
v-for="(domain, index) in urlList" v-for="(domain, index) in urlList"
:key="index" :key="index"
v-model="urlList[index]" v-model="urlList[index]"
size="sm" size="sm"
style="margin-top: 5px"> style="margin-top: 5px">
<template #actions> <template #actions>
<Button variant="ghost" @click="urlList.splice(index, 1)"><Trash2 /></Button> <Button variant="ghost" @click="urlList.splice(index, 1)"><Trash2 /></Button>
</template> </template>
</InputGroupAction> </InputGroupAction>
<Button size="sm" variant="outline" style="margin-top: 5px" @click="urlList.push('')"> <Button size="sm" variant="outline" style="margin-top: 5px" @click="urlList.push('')">
{{ t('dialog.allowed_video_player_domains.add_domain') }} {{ t('dialog.allowed_video_player_domains.add_domain') }}
</Button> </Button>
</div> </div>
<template #footer>
<Button :disabled="!worldAllowedDomainsDialog.worldId" @click="saveWorldAllowedDomains"> <DialogFooter>
{{ t('dialog.allowed_video_player_domains.save') }} <Button :disabled="!worldAllowedDomainsDialog.worldId" @click="saveWorldAllowedDomains">
</Button> {{ t('dialog.allowed_video_player_domains.save') }}
</template> </Button>
</el-dialog> </DialogFooter>
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { InputGroupAction } from '@/components/ui/input-group'; import { InputGroupAction } from '@/components/ui/input-group';

File diff suppressed because it is too large Load Diff

View File

@@ -44,101 +44,105 @@
<span>{{ t('view.charts.mutual_friend.progress.no_relationships_discovered') }}</span> <span>{{ t('view.charts.mutual_friend.progress.no_relationships_discovered') }}</span>
</div> </div>
<el-dialog <Dialog v-model:open="isForceDialogVisible">
v-model="isForceDialogVisible" <DialogContent>
:title="t('view.charts.mutual_friend.force_dialog.title')" <DialogHeader>
width="440px"> <DialogTitle>{{ t('view.charts.mutual_friend.force_dialog.title') }}</DialogTitle>
<p class="mutual-graph__force-description"> </DialogHeader>
{{ t('view.charts.mutual_friend.force_dialog.description') }}
</p>
<FieldGroup class="mutual-graph__force-form">
<Field>
<FieldLabel>{{ t('view.charts.mutual_friend.force_dialog.repulsion') }}</FieldLabel>
<FieldContent>
<NumberField
v-model="forceForm.repulsion"
:step="1"
:format-options="{ maximumFractionDigits: 0 }"
class="mutual-graph__number-input">
<NumberFieldContent>
<NumberFieldInput />
</NumberFieldContent>
</NumberField>
<FieldDescription class="mutual-graph__helper">
{{ t('view.charts.mutual_friend.force_dialog.repulsion_help') }}
</FieldDescription>
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('view.charts.mutual_friend.force_dialog.edge_length_min') }}</FieldLabel>
<FieldContent>
<NumberField
v-model="forceForm.edgeLengthMin"
:step="1"
:format-options="{ maximumFractionDigits: 0 }"
class="mutual-graph__number-input">
<NumberFieldContent>
<NumberFieldInput />
</NumberFieldContent>
</NumberField>
<FieldDescription class="mutual-graph__helper">
{{ t('view.charts.mutual_friend.force_dialog.edge_length_min_help') }}
</FieldDescription>
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('view.charts.mutual_friend.force_dialog.edge_length_max') }}</FieldLabel>
<FieldContent>
<NumberField
v-model="forceForm.edgeLengthMax"
:step="1"
:format-options="{ maximumFractionDigits: 0 }"
class="mutual-graph__number-input">
<NumberFieldContent>
<NumberFieldInput />
</NumberFieldContent>
</NumberField>
<FieldDescription class="mutual-graph__helper">
{{ t('view.charts.mutual_friend.force_dialog.edge_length_max_help') }}
</FieldDescription>
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('view.charts.mutual_friend.force_dialog.gravity') }}</FieldLabel>
<FieldContent>
<NumberField
v-model="forceForm.gravity"
:max="1"
:step="0.1"
:format-options="{ maximumFractionDigits: 1 }"
class="mutual-graph__number-input">
<NumberFieldContent>
<NumberFieldInput />
</NumberFieldContent>
</NumberField>
<FieldDescription class="mutual-graph__helper">
{{ t('view.charts.mutual_friend.force_dialog.gravity_help') }}
</FieldDescription>
</FieldContent>
</Field>
</FieldGroup>
<template #footer> <p class="mutual-graph__force-description">
<div class="mutual-graph__dialog-footer"> {{ t('view.charts.mutual_friend.force_dialog.description') }}
<Button variant="secondary" class="mr-2" @click="resetForceSettings">{{ </p>
t('view.charts.mutual_friend.force_dialog.reset') <FieldGroup class="mutual-graph__force-form">
}}</Button> <Field>
<Button :disabled="!graphReady" @click="applyForceSettings"> <FieldLabel>{{ t('view.charts.mutual_friend.force_dialog.repulsion') }}</FieldLabel>
{{ t('view.charts.mutual_friend.force_dialog.apply') }} <FieldContent>
</Button> <NumberField
</div> v-model="forceForm.repulsion"
</template> :step="1"
</el-dialog> :format-options="{ maximumFractionDigits: 0 }"
class="mutual-graph__number-input">
<NumberFieldContent>
<NumberFieldInput />
</NumberFieldContent>
</NumberField>
<FieldDescription class="mutual-graph__helper">
{{ t('view.charts.mutual_friend.force_dialog.repulsion_help') }}
</FieldDescription>
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('view.charts.mutual_friend.force_dialog.edge_length_min') }}</FieldLabel>
<FieldContent>
<NumberField
v-model="forceForm.edgeLengthMin"
:step="1"
:format-options="{ maximumFractionDigits: 0 }"
class="mutual-graph__number-input">
<NumberFieldContent>
<NumberFieldInput />
</NumberFieldContent>
</NumberField>
<FieldDescription class="mutual-graph__helper">
{{ t('view.charts.mutual_friend.force_dialog.edge_length_min_help') }}
</FieldDescription>
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('view.charts.mutual_friend.force_dialog.edge_length_max') }}</FieldLabel>
<FieldContent>
<NumberField
v-model="forceForm.edgeLengthMax"
:step="1"
:format-options="{ maximumFractionDigits: 0 }"
class="mutual-graph__number-input">
<NumberFieldContent>
<NumberFieldInput />
</NumberFieldContent>
</NumberField>
<FieldDescription class="mutual-graph__helper">
{{ t('view.charts.mutual_friend.force_dialog.edge_length_max_help') }}
</FieldDescription>
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('view.charts.mutual_friend.force_dialog.gravity') }}</FieldLabel>
<FieldContent>
<NumberField
v-model="forceForm.gravity"
:max="1"
:step="0.1"
:format-options="{ maximumFractionDigits: 1 }"
class="mutual-graph__number-input">
<NumberFieldContent>
<NumberFieldInput />
</NumberFieldContent>
</NumberField>
<FieldDescription class="mutual-graph__helper">
{{ t('view.charts.mutual_friend.force_dialog.gravity_help') }}
</FieldDescription>
</FieldContent>
</Field>
</FieldGroup>
<DialogFooter>
<div class="mutual-graph__dialog-footer">
<Button variant="secondary" class="mr-2" @click="resetForceSettings">{{
t('view.charts.mutual_friend.force_dialog.reset')
}}</Button>
<Button :disabled="!graphReady" @click="applyForceSettings">
{{ t('view.charts.mutual_friend.force_dialog.apply') }}
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'; import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel } from '@/components/ui/field'; import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel } from '@/components/ui/field';
import { NumberField, NumberFieldContent, NumberFieldInput } from '@/components/ui/number-field'; import { NumberField, NumberFieldContent, NumberFieldInput } from '@/components/ui/number-field';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';

View File

@@ -1,64 +1,71 @@
<template> <template>
<el-dialog v-model="isDialogVisible" :title="t('dialog.avatar_export.header')" width="650px"> <Dialog v-model:open="isDialogVisible">
<div style="margin-bottom: 10px" class="flex flex-col gap-2"> <DialogContent>
<label v-for="option in exportSelectOptions" :key="option.value" class="inline-flex items-center gap-2"> <DialogHeader>
<Checkbox <DialogTitle>{{ t('dialog.avatar_export.header') }}</DialogTitle>
:model-value="exportSelectedOptions.includes(option.label)" </DialogHeader>
@update:modelValue="(val) => toggleAvatarExportOption(option.label, val)" />
<span>{{ option.label }}</span>
</label>
</div>
<div class="flex items-center gap-2"> <div style="margin-bottom: 10px" class="flex flex-col gap-2">
<Select <label v-for="option in exportSelectOptions" :key="option.value" class="inline-flex items-center gap-2">
:model-value="avatarExportFavoriteGroupSelection" <Checkbox
@update:modelValue="handleAvatarExportFavoriteGroupSelect"> :model-value="exportSelectedOptions.includes(option.label)"
<SelectTrigger size="sm"> @update:modelValue="(val) => toggleAvatarExportOption(option.label, val)" />
<SelectValue placeholder="All Favorites" /> <span>{{ option.label }}</span>
</SelectTrigger> </label>
<SelectContent> </div>
<SelectGroup>
<SelectItem :value="AVATAR_EXPORT_ALL_VALUE">All Favorites</SelectItem>
<SelectItem
v-for="groupAPI in favoriteAvatarGroups"
:key="groupAPI.name"
:value="groupAPI.name">
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<Select <div class="flex items-center gap-2">
:model-value="avatarExportLocalFavoriteGroupSelection" <Select
@update:modelValue="handleAvatarExportLocalFavoriteGroupSelect" :model-value="avatarExportFavoriteGroupSelection"
style="margin-left: 10px"> @update:modelValue="handleAvatarExportFavoriteGroupSelect">
<SelectTrigger size="sm"> <SelectTrigger size="sm">
<SelectValue placeholder="Select Group" /> <SelectValue placeholder="All Favorites" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectItem :value="AVATAR_EXPORT_NONE_VALUE">None</SelectItem> <SelectItem :value="AVATAR_EXPORT_ALL_VALUE">All Favorites</SelectItem>
<SelectItem v-for="group in localAvatarFavoriteGroups" :key="group" :value="group"> <SelectItem
{{ group }} ({{ localAvatarFavGroupLength(group) }}) v-for="groupAPI in favoriteAvatarGroups"
</SelectItem> :key="groupAPI.name"
</SelectGroup> :value="groupAPI.name">
</SelectContent> {{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
</Select> </SelectItem>
</div> </SelectGroup>
<br /> </SelectContent>
<InputGroupTextareaField </Select>
v-model="avatarExportContent"
:rows="15" <Select
readonly :model-value="avatarExportLocalFavoriteGroupSelection"
style="margin-top: 15px" @update:modelValue="handleAvatarExportLocalFavoriteGroupSelect"
input-class="resize-none" style="margin-left: 10px">
@click="handleCopyAvatarExportData" /> <SelectTrigger size="sm">
</el-dialog> <SelectValue placeholder="Select Group" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem :value="AVATAR_EXPORT_NONE_VALUE">None</SelectItem>
<SelectItem v-for="group in localAvatarFavoriteGroups" :key="group" :value="group">
{{ group }} ({{ localAvatarFavGroupLength(group) }})
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
<br />
<InputGroupTextareaField
v-model="avatarExportContent"
:rows="15"
readonly
style="margin-top: 15px"
input-class="resize-none"
@click="handleCopyAvatarExportData" />
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';

View File

@@ -1,120 +1,122 @@
<template> <template>
<el-dialog <Dialog v-model:open="isVisible">
:z-index="avatarImportDialogIndex" <DialogContent>
v-model="isVisible" <DialogHeader>
:title="t('dialog.avatar_import.header')" <DialogTitle>{{ t('dialog.avatar_import.header') }}</DialogTitle>
width="650px"> </DialogHeader>
<div style="display: flex; align-items: center; justify-content: space-between"> <div style="display: flex; align-items: center; justify-content: space-between">
<div style="font-size: 12px">{{ t('dialog.avatar_import.description') }}</div> <div style="font-size: 12px">{{ t('dialog.avatar_import.description') }}</div>
<div style="display: flex; align-items: center"> <div style="display: flex; align-items: center">
<div v-if="avatarImportDialog.progress"> <div v-if="avatarImportDialog.progress">
{{ t('dialog.avatar_import.process_progress') }} {{ avatarImportDialog.progress }} / {{ t('dialog.avatar_import.process_progress') }} {{ avatarImportDialog.progress }} /
{{ avatarImportDialog.progressTotal }} {{ avatarImportDialog.progressTotal }}
<Loader2 style="margin: 0 5px" /> <Loader2 style="margin: 0 5px" />
</div>
<Button v-if="avatarImportDialog.loading" size="sm" variant="secondary" @click="cancelAvatarImport">
{{ t('dialog.avatar_import.cancel') }}
</Button>
<Button size="sm" v-else :disabled="!avatarImportDialog.input" @click="processAvatarImportList">
{{ t('dialog.avatar_import.process_list') }}
</Button>
</div> </div>
<Button v-if="avatarImportDialog.loading" size="sm" variant="secondary" @click="cancelAvatarImport">
{{ t('dialog.avatar_import.cancel') }}
</Button>
<Button size="sm" v-else :disabled="!avatarImportDialog.input" @click="processAvatarImportList">
{{ t('dialog.avatar_import.process_list') }}
</Button>
</div> </div>
</div> <InputGroupTextareaField
<InputGroupTextareaField v-model="avatarImportDialog.input"
v-model="avatarImportDialog.input" :rows="10"
:rows="10" style="margin-top: 10px"
style="margin-top: 10px" input-class="resize-none" />
input-class="resize-none" /> <div style="display: flex; align-items: center; justify-content: space-between; margin-top: 5px">
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 5px"> <div>
<div> <div class="flex items-center gap-2">
<div class="flex items-center gap-2"> <Select
<Select :model-value="avatarImportFavoriteGroupSelection"
:model-value="avatarImportFavoriteGroupSelection" @update:modelValue="handleAvatarImportGroupSelect"
@update:modelValue="handleAvatarImportGroupSelect" style="margin-right: 5px">
style="margin-right: 5px"> <SelectTrigger size="sm">
<SelectTrigger size="sm"> <SelectValue :placeholder="t('dialog.avatar_import.select_group_placeholder')" />
<SelectValue :placeholder="t('dialog.avatar_import.select_group_placeholder')" /> </SelectTrigger>
</SelectTrigger> <SelectContent>
<SelectContent> <SelectGroup>
<SelectGroup> <SelectItem
<SelectItem v-for="groupAPI in favoriteAvatarGroups"
v-for="groupAPI in favoriteAvatarGroups" :key="groupAPI.name"
:key="groupAPI.name" :value="groupAPI.name"
:value="groupAPI.name" :disabled="groupAPI.count >= groupAPI.capacity">
:disabled="groupAPI.count >= groupAPI.capacity"> {{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }}) </SelectItem>
</SelectItem> </SelectGroup>
</SelectGroup> </SelectContent>
</SelectContent> </Select>
</Select>
<Select <Select
:model-value="avatarImportLocalFavoriteGroupSelection" :model-value="avatarImportLocalFavoriteGroupSelection"
@update:modelValue="handleAvatarImportLocalGroupSelect" @update:modelValue="handleAvatarImportLocalGroupSelect"
style="margin-left: 10px"> style="margin-left: 10px">
<SelectTrigger size="sm"> <SelectTrigger size="sm">
<SelectValue :placeholder="t('dialog.avatar_import.select_group_placeholder')" /> <SelectValue :placeholder="t('dialog.avatar_import.select_group_placeholder')" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectItem v-for="group in localAvatarFavoriteGroups" :key="group" :value="group"> <SelectItem v-for="group in localAvatarFavoriteGroups" :key="group" :value="group">
{{ group }} ({{ localAvatarFavGroupLength(group) }}) {{ group }} ({{ localAvatarFavGroupLength(group) }})
</SelectItem> </SelectItem>
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
</Select> </Select>
</div>
<span v-if="avatarImportDialog.avatarImportFavoriteGroup" style="margin-left: 5px">
{{ avatarImportTable.data.length }} /
{{
avatarImportDialog.avatarImportFavoriteGroup.capacity -
avatarImportDialog.avatarImportFavoriteGroup.count
}}
</span>
</div>
<div>
<Button size="sm" variant="secondary" class="mr-2" @click="clearAvatarImportTable">
{{ t('dialog.avatar_import.clear_table') }}
</Button>
<Button
size="sm"
:disabled="
avatarImportTable.data.length === 0 ||
(!avatarImportDialog.avatarImportFavoriteGroup &&
!avatarImportDialog.avatarImportLocalFavoriteGroup)
"
@click="importAvatarImportTable">
{{ t('dialog.avatar_import.import') }}
</Button>
</div> </div>
<span v-if="avatarImportDialog.avatarImportFavoriteGroup" style="margin-left: 5px">
{{ avatarImportTable.data.length }} /
{{
avatarImportDialog.avatarImportFavoriteGroup.capacity -
avatarImportDialog.avatarImportFavoriteGroup.count
}}
</span>
</div> </div>
<div> <span v-if="avatarImportDialog.importProgress" style="margin: 10px">
<Button size="sm" variant="secondary" class="mr-2" @click="clearAvatarImportTable"> <Loader2 style="margin-right: 5px" />
{{ t('dialog.avatar_import.clear_table') }} {{ t('dialog.avatar_import.import_progress') }}
{{ avatarImportDialog.importProgress }}/{{ avatarImportDialog.importProgressTotal }}
</span>
<br />
<template v-if="avatarImportDialog.errors">
<Button size="sm" variant="secondary" @click="avatarImportDialog.errors = ''">
{{ t('dialog.avatar_import.clear_errors') }}
</Button> </Button>
<Button <h2 style="font-weight: bold; margin: 5px 0">
size="sm" {{ t('dialog.avatar_import.errors') }}
:disabled=" </h2>
avatarImportTable.data.length === 0 || <pre style="white-space: pre-wrap; font-size: 12px" v-text="avatarImportDialog.errors"></pre>
(!avatarImportDialog.avatarImportFavoriteGroup && </template>
!avatarImportDialog.avatarImportLocalFavoriteGroup) <DataTableLayout
" class="min-w-0 w-full"
@click="importAvatarImportTable"> :table="table"
{{ t('dialog.avatar_import.import') }} :loading="avatarImportDialog.loading"
</Button> :table-style="tableStyle"
</div> :show-pagination="false"
</div> style="margin-top: 10px" />
<span v-if="avatarImportDialog.importProgress" style="margin: 10px"> </DialogContent>
<Loader2 style="margin-right: 5px" /> </Dialog>
{{ t('dialog.avatar_import.import_progress') }}
{{ avatarImportDialog.importProgress }}/{{ avatarImportDialog.importProgressTotal }}
</span>
<br />
<template v-if="avatarImportDialog.errors">
<Button size="sm" variant="secondary" @click="avatarImportDialog.errors = ''">
{{ t('dialog.avatar_import.clear_errors') }}
</Button>
<h2 style="font-weight: bold; margin: 5px 0">
{{ t('dialog.avatar_import.errors') }}
</h2>
<pre style="white-space: pre-wrap; font-size: 12px" v-text="avatarImportDialog.errors"></pre>
</template>
<DataTableLayout
class="min-w-0 w-full"
:table="table"
:loading="avatarImportDialog.loading"
:table-style="tableStyle"
:show-pagination="false"
style="margin-top: 10px" />
</el-dialog>
</template> </template>
<script setup> <script setup>
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { DataTableLayout } from '@/components/ui/data-table'; import { DataTableLayout } from '@/components/ui/data-table';
@@ -127,7 +129,6 @@
import { useAvatarStore, useFavoriteStore, useGalleryStore, useUserStore } from '../../../stores'; import { useAvatarStore, useFavoriteStore, useGalleryStore, useUserStore } from '../../../stores';
import { avatarRequest, favoriteRequest } from '../../../api'; import { avatarRequest, favoriteRequest } from '../../../api';
import { createColumns } from './avatarImportColumns.jsx'; import { createColumns } from './avatarImportColumns.jsx';
import { getNextDialogIndex } from '../../../shared/utils/base/ui';
import { removeFromArray } from '../../../shared/utils'; import { removeFromArray } from '../../../shared/utils';
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable'; import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
@@ -189,8 +190,6 @@
enableSorting: false enableSorting: false
}); });
const avatarImportDialogIndex = ref(2000);
const isVisible = computed({ const isVisible = computed({
get() { get() {
return avatarImportDialogVisible.value; return avatarImportDialogVisible.value;
@@ -204,7 +203,6 @@
() => avatarImportDialogVisible.value, () => avatarImportDialogVisible.value,
(value) => { (value) => {
if (value) { if (value) {
avatarImportDialogIndex.value = getNextDialogIndex();
clearAvatarImportTable(); clearAvatarImportTable();
resetAvatarImport(); resetAvatarImport();
if (avatarImportDialogInput.value) { if (avatarImportDialogInput.value) {

View File

@@ -1,41 +1,47 @@
<template> <template>
<el-dialog <Dialog v-model:open="isDialogVisible">
v-model="isDialogVisible" <DialogContent>
class="x-dialog" <DialogHeader>
:title="t('dialog.friend_export.header')" <DialogTitle>{{ t('dialog.friend_export.header') }}</DialogTitle>
width="650px" </DialogHeader>
destroy-on-close> <Select
<Select :model-value="friendExportFavoriteGroupSelection" @update:modelValue="handleFriendExportGroupSelect"> :model-value="friendExportFavoriteGroupSelection"
<SelectTrigger size="sm"> @update:modelValue="handleFriendExportGroupSelect">
<SelectValue placeholder="All Favorites" /> <SelectTrigger size="sm">
</SelectTrigger> <SelectValue placeholder="All Favorites" />
<SelectContent> </SelectTrigger>
<SelectGroup> <SelectContent>
<SelectItem :value="FRIEND_EXPORT_ALL_VALUE">All Favorites</SelectItem> <SelectGroup>
<SelectItem v-for="groupAPI in favoriteFriendGroups" :key="groupAPI.name" :value="groupAPI.name"> <SelectItem :value="FRIEND_EXPORT_ALL_VALUE">All Favorites</SelectItem>
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }}) <SelectItem
</SelectItem> v-for="groupAPI in favoriteFriendGroups"
</SelectGroup> :key="groupAPI.name"
</SelectContent> :value="groupAPI.name">
</Select> {{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<br /> <br />
<InputGroupTextareaField <InputGroupTextareaField
v-model="friendExportContent" v-model="friendExportContent"
:rows="15" :rows="15"
readonly readonly
style="margin-top: 15px" style="margin-top: 15px"
input-class="resize-none" input-class="resize-none"
@click="handleCopyFriendExportData" /> @click="handleCopyFriendExportData" />
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';
import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';

View File

@@ -1,101 +1,103 @@
<template> <template>
<el-dialog <Dialog v-model:open="isVisible">
:z-index="friendImportDialogIndex" <DialogContent>
v-model="isVisible" <DialogHeader>
:title="t('dialog.friend_import.header')" <DialogTitle>{{ t('dialog.friend_import.header') }}</DialogTitle>
width="650px"> </DialogHeader>
<div style="display: flex; align-items: center; justify-content: space-between"> <div style="display: flex; align-items: center; justify-content: space-between">
<div style="font-size: 12px">{{ t('dialog.friend_import.description') }}</div> <div style="font-size: 12px">{{ t('dialog.friend_import.description') }}</div>
<div style="display: flex; align-items: center"> <div style="display: flex; align-items: center">
<div v-if="friendImportDialog.progress"> <div v-if="friendImportDialog.progress">
{{ t('dialog.friend_import.process_progress') }} {{ friendImportDialog.progress }} / {{ t('dialog.friend_import.process_progress') }} {{ friendImportDialog.progress }} /
{{ friendImportDialog.progressTotal }} {{ friendImportDialog.progressTotal }}
<Loader2 style="margin: 0 5px" /> <Loader2 style="margin: 0 5px" />
</div>
<Button v-if="friendImportDialog.loading" size="sm" variant="secondary" @click="cancelFriendImport">
{{ t('dialog.friend_import.cancel') }}
</Button>
<Button size="sm" v-else :disabled="!friendImportDialog.input" @click="processFriendImportList">
{{ t('dialog.friend_import.process_list') }}
</Button>
</div> </div>
<Button v-if="friendImportDialog.loading" size="sm" variant="secondary" @click="cancelFriendImport">
{{ t('dialog.friend_import.cancel') }}
</Button>
<Button size="sm" v-else :disabled="!friendImportDialog.input" @click="processFriendImportList">
{{ t('dialog.friend_import.process_list') }}
</Button>
</div> </div>
</div> <InputGroupTextareaField
<InputGroupTextareaField v-model="friendImportDialog.input"
v-model="friendImportDialog.input" :rows="10"
:rows="10" style="margin-top: 10px"
style="margin-top: 10px" input-class="resize-none" />
input-class="resize-none" /> <div style="display: flex; align-items: center; justify-content: space-between; margin-top: 5px">
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 5px"> <div>
<div> <Select
<Select :model-value="friendImportFavoriteGroupSelection"
:model-value="friendImportFavoriteGroupSelection" @update:modelValue="handleFriendImportGroupSelect">
@update:modelValue="handleFriendImportGroupSelect"> <SelectTrigger size="sm">
<SelectTrigger size="sm"> <SelectValue :placeholder="t('dialog.friend_import.select_group_placeholder')" />
<SelectValue :placeholder="t('dialog.friend_import.select_group_placeholder')" /> </SelectTrigger>
</SelectTrigger> <SelectContent>
<SelectContent> <SelectGroup>
<SelectGroup> <SelectItem
<SelectItem v-for="groupAPI in favoriteFriendGroups"
v-for="groupAPI in favoriteFriendGroups" :key="groupAPI.name"
:key="groupAPI.name" :value="groupAPI.name"
:value="groupAPI.name" :disabled="groupAPI.count >= groupAPI.capacity">
:disabled="groupAPI.count >= groupAPI.capacity"> {{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }}) </SelectItem>
</SelectItem> </SelectGroup>
</SelectGroup> </SelectContent>
</SelectContent> </Select>
</Select> <span v-if="friendImportDialog.friendImportFavoriteGroup" style="margin-left: 5px">
<span v-if="friendImportDialog.friendImportFavoriteGroup" style="margin-left: 5px"> {{ friendImportTable.data.length }} /
{{ friendImportTable.data.length }} / {{
{{ friendImportDialog.friendImportFavoriteGroup.capacity -
friendImportDialog.friendImportFavoriteGroup.capacity - friendImportDialog.friendImportFavoriteGroup.count
friendImportDialog.friendImportFavoriteGroup.count }}
}} </span>
</span> </div>
<div>
<Button
size="sm"
class="mr-2"
variant="secondary"
:disabled="friendImportTable.data.length === 0"
@click="clearFriendImportTable">
{{ t('dialog.friend_import.clear_table') }}
</Button>
<Button
size="sm"
:disabled="friendImportTable.data.length === 0 || !friendImportDialog.friendImportFavoriteGroup"
@click="importFriendImportTable">
{{ t('dialog.friend_import.import') }}
</Button>
</div>
</div> </div>
<div> <span v-if="friendImportDialog.importProgress" style="margin: 10px">
<Button <Loader2 style="margin-right: 5px" />
size="sm" {{ t('dialog.friend_import.import_progress') }} {{ friendImportDialog.importProgress }}/{{
class="mr-2" friendImportDialog.importProgressTotal
variant="secondary" }}
:disabled="friendImportTable.data.length === 0" </span>
@click="clearFriendImportTable"> <br />
{{ t('dialog.friend_import.clear_table') }} <template v-if="friendImportDialog.errors">
<Button size="sm" variant="secondary" @click="friendImportDialog.errors = ''">
{{ t('dialog.friend_import.clear_errors') }}
</Button> </Button>
<Button <h2 style="font-weight: bold; margin: 5px 0">{{ t('dialog.friend_import.errors') }}</h2>
size="sm" <pre style="white-space: pre-wrap; font-size: 12px" v-text="friendImportDialog.errors"></pre>
:disabled="friendImportTable.data.length === 0 || !friendImportDialog.friendImportFavoriteGroup" </template>
@click="importFriendImportTable"> <DataTableLayout
{{ t('dialog.friend_import.import') }} class="min-w-0 w-full"
</Button> :table="table"
</div> :loading="friendImportDialog.loading"
</div> :table-style="tableStyle"
<span v-if="friendImportDialog.importProgress" style="margin: 10px"> :show-pagination="false"
<Loader2 style="margin-right: 5px" /> style="margin-top: 10px" />
{{ t('dialog.friend_import.import_progress') }} {{ friendImportDialog.importProgress }}/{{ </DialogContent>
friendImportDialog.importProgressTotal </Dialog>
}}
</span>
<br />
<template v-if="friendImportDialog.errors">
<Button size="sm" variant="secondary" @click="friendImportDialog.errors = ''">
{{ t('dialog.friend_import.clear_errors') }}
</Button>
<h2 style="font-weight: bold; margin: 5px 0">{{ t('dialog.friend_import.errors') }}</h2>
<pre style="white-space: pre-wrap; font-size: 12px" v-text="friendImportDialog.errors"></pre>
</template>
<DataTableLayout
class="min-w-0 w-full"
:table="table"
:loading="friendImportDialog.loading"
:table-style="tableStyle"
:show-pagination="false"
style="margin-top: 10px" />
</el-dialog>
</template> </template>
<script setup> <script setup>
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { DataTableLayout } from '@/components/ui/data-table'; import { DataTableLayout } from '@/components/ui/data-table';
@@ -109,7 +111,6 @@
import { useFavoriteStore, useGalleryStore, useUserStore } from '../../../stores'; import { useFavoriteStore, useGalleryStore, useUserStore } from '../../../stores';
import { favoriteRequest, userRequest } from '../../../api'; import { favoriteRequest, userRequest } from '../../../api';
import { createColumns } from './friendImportColumns.jsx'; import { createColumns } from './friendImportColumns.jsx';
import { getNextDialogIndex } from '../../../shared/utils/base/ui';
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable'; import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
const { t } = useI18n(); const { t } = useI18n();
@@ -169,8 +170,6 @@
enableSorting: false enableSorting: false
}); });
const friendImportDialogIndex = ref(2000);
const isVisible = computed({ const isVisible = computed({
get() { get() {
return friendImportDialogVisible.value; return friendImportDialogVisible.value;
@@ -184,7 +183,6 @@
() => friendImportDialogVisible.value, () => friendImportDialogVisible.value,
(value) => { (value) => {
if (value) { if (value) {
friendImportDialogIndex.value = getNextDialogIndex();
clearFriendImportTable(); clearFriendImportTable();
resetFriendImport(); resetFriendImport();
friendImportFavoriteGroupSelection.value = friendImportFavoriteGroupSelection.value =

View File

@@ -1,61 +1,73 @@
<template> <template>
<el-dialog v-model="isDialogVisible" :title="t('dialog.world_export.header')" width="650px"> <Dialog v-model:open="isDialogVisible">
<div style="margin-bottom: 10px" class="flex flex-col gap-2"> <DialogContent>
<label v-for="option in exportSelectOptions" :key="option.value" class="inline-flex items-center gap-2"> <DialogHeader>
<Checkbox <DialogTitle>{{ t('dialog.world_export.header') }}</DialogTitle>
:model-value="exportSelectedOptions.includes(option.label)" </DialogHeader>
@update:modelValue="(val) => toggleWorldExportOption(option.label, val)" />
<span>{{ option.label }}</span>
</label>
</div>
<div class="flex items-center gap-2"> <div style="margin-bottom: 10px" class="flex flex-col gap-2">
<Select :model-value="worldExportFavoriteGroupSelection" @update:modelValue="handleWorldExportGroupSelect"> <label v-for="option in exportSelectOptions" :key="option.value" class="inline-flex items-center gap-2">
<SelectTrigger size="sm"> <Checkbox
<SelectValue placeholder="All Favorites" /> :model-value="exportSelectedOptions.includes(option.label)"
</SelectTrigger> @update:modelValue="(val) => toggleWorldExportOption(option.label, val)" />
<SelectContent> <span>{{ option.label }}</span>
<SelectGroup> </label>
<SelectItem :value="WORLD_EXPORT_ALL_VALUE">None</SelectItem> </div>
<SelectItem v-for="groupAPI in favoriteWorldGroups" :key="groupAPI.name" :value="groupAPI.name">
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<Select <div class="flex items-center gap-2">
:model-value="worldExportLocalFavoriteGroupSelection" <Select
@update:modelValue="handleWorldExportLocalGroupSelect" :model-value="worldExportFavoriteGroupSelection"
style="margin-left: 10px"> @update:modelValue="handleWorldExportGroupSelect">
<SelectTrigger size="sm"> <SelectTrigger size="sm">
<SelectValue placeholder="Select Group" /> <SelectValue placeholder="All Favorites" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectItem :value="WORLD_EXPORT_NONE_VALUE">None</SelectItem> <SelectItem :value="WORLD_EXPORT_ALL_VALUE">None</SelectItem>
<SelectItem v-for="group in localWorldFavoriteGroups" :key="group" :value="group"> <SelectItem
{{ group }} ({{ localWorldFavorites[group].length }}) v-for="groupAPI in favoriteWorldGroups"
</SelectItem> :key="groupAPI.name"
</SelectGroup> :value="groupAPI.name">
</SelectContent> {{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
</Select> </SelectItem>
</div> </SelectGroup>
</SelectContent>
</Select>
<br /> <Select
:model-value="worldExportLocalFavoriteGroupSelection"
@update:modelValue="handleWorldExportLocalGroupSelect"
style="margin-left: 10px">
<SelectTrigger size="sm">
<SelectValue placeholder="Select Group" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem :value="WORLD_EXPORT_NONE_VALUE">None</SelectItem>
<SelectItem v-for="group in localWorldFavoriteGroups" :key="group" :value="group">
{{ group }} ({{ localWorldFavorites[group].length }})
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
<InputGroupTextareaField <br />
v-model="worldExportContent"
:rows="15" <InputGroupTextareaField
readonly v-model="worldExportContent"
style="margin-top: 15px" :rows="15"
input-class="resize-none" readonly
@click="handleCopyWorldExportData" /> style="margin-top: 15px"
</el-dialog> input-class="resize-none"
@click="handleCopyWorldExportData" />
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';

View File

@@ -1,125 +1,126 @@
<template> <template>
<el-dialog <Dialog v-model:open="isVisible">
:z-index="worldImportDialogIndex" <DialogContent>
v-model="isVisible" <DialogHeader>
:title="t('dialog.world_import.header')" <DialogTitle>{{ t('dialog.world_import.header') }}</DialogTitle>
width="650px" </DialogHeader>
class="x-dialog"> <div style="display: flex; align-items: center; justify-content: space-between">
<div style="display: flex; align-items: center; justify-content: space-between"> <div style="font-size: 12px">{{ t('dialog.world_import.description') }}</div>
<div style="font-size: 12px">{{ t('dialog.world_import.description') }}</div> <div style="display: flex; align-items: center">
<div style="display: flex; align-items: center"> <div v-if="worldImportDialog.progress">
<div v-if="worldImportDialog.progress"> {{ t('dialog.world_import.process_progress') }}
{{ t('dialog.world_import.process_progress') }} {{ worldImportDialog.progress }} / {{ worldImportDialog.progressTotal }}
{{ worldImportDialog.progress }} / {{ worldImportDialog.progressTotal }} <Loader2 style="margin: 0 5px" />
<Loader2 style="margin: 0 5px" /> </div>
<Button v-if="worldImportDialog.loading" size="sm" variant="outline" @click="cancelWorldImport">
{{ t('dialog.world_import.cancel') }}
</Button>
<Button size="sm" v-else :disabled="!worldImportDialog.input" @click="processWorldImportList">
{{ t('dialog.world_import.process_list') }}
</Button>
</div> </div>
<Button v-if="worldImportDialog.loading" size="sm" variant="outline" @click="cancelWorldImport">
{{ t('dialog.world_import.cancel') }}
</Button>
<Button size="sm" v-else :disabled="!worldImportDialog.input" @click="processWorldImportList">
{{ t('dialog.world_import.process_list') }}
</Button>
</div> </div>
</div> <InputGroupTextareaField
<InputGroupTextareaField v-model="worldImportDialog.input"
v-model="worldImportDialog.input" :rows="10"
:rows="10" style="margin-top: 10px"
style="margin-top: 10px" input-class="resize-none" />
input-class="resize-none" /> <div style="display: flex; align-items: center; justify-content: space-between; margin-top: 5px">
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 5px"> <div>
<div> <div class="flex items-center gap-2">
<div class="flex items-center gap-2"> <Select
<Select :model-value="worldImportFavoriteGroupSelection"
:model-value="worldImportFavoriteGroupSelection" @update:modelValue="handleWorldImportGroupSelect">
@update:modelValue="handleWorldImportGroupSelect"> <SelectTrigger size="sm">
<SelectTrigger size="sm"> <SelectValue :placeholder="t('dialog.world_import.select_vrchat_group_placeholder')" />
<SelectValue :placeholder="t('dialog.world_import.select_vrchat_group_placeholder')" /> </SelectTrigger>
</SelectTrigger> <SelectContent>
<SelectContent> <SelectGroup>
<SelectGroup> <SelectItem
<SelectItem v-for="groupAPI in favoriteWorldGroups"
v-for="groupAPI in favoriteWorldGroups" :key="groupAPI.name"
:key="groupAPI.name" :value="groupAPI.name"
:value="groupAPI.name" :disabled="groupAPI.count >= groupAPI.capacity">
:disabled="groupAPI.count >= groupAPI.capacity"> {{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }}) </SelectItem>
</SelectItem> </SelectGroup>
</SelectGroup> </SelectContent>
</SelectContent> </Select>
</Select>
<Select <Select
:model-value="worldImportLocalFavoriteGroupSelection" :model-value="worldImportLocalFavoriteGroupSelection"
@update:modelValue="handleWorldImportLocalGroupSelect" @update:modelValue="handleWorldImportLocalGroupSelect"
style="margin-left: 10px"> style="margin-left: 10px">
<SelectTrigger size="sm"> <SelectTrigger size="sm">
<SelectValue :placeholder="t('dialog.world_import.select_local_group_placeholder')" /> <SelectValue :placeholder="t('dialog.world_import.select_local_group_placeholder')" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectItem v-for="group in localWorldFavoriteGroups" :key="group" :value="group"> <SelectItem v-for="group in localWorldFavoriteGroups" :key="group" :value="group">
{{ group }} ({{ localWorldFavGroupLength(group) }}) {{ group }} ({{ localWorldFavGroupLength(group) }})
</SelectItem> </SelectItem>
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
</Select> </Select>
</div>
<span v-if="worldImportDialog.worldImportFavoriteGroup" style="margin-left: 5px">
{{ worldImportTable.data.length }} /
{{
worldImportDialog.worldImportFavoriteGroup.capacity -
worldImportDialog.worldImportFavoriteGroup.count
}}
</span>
</div>
<div>
<Button
size="sm"
variant="secondary"
class="mr-2"
:disabled="worldImportTable.data.length === 0"
@click="clearWorldImportTable">
{{ t('dialog.world_import.clear_table') }}
</Button>
<Button
size="sm"
:disabled="
worldImportTable.data.length === 0 ||
(!worldImportDialog.worldImportFavoriteGroup &&
!worldImportDialog.worldImportLocalFavoriteGroup)
"
@click="importWorldImportTable">
{{ t('dialog.world_import.import') }}
</Button>
</div> </div>
<span v-if="worldImportDialog.worldImportFavoriteGroup" style="margin-left: 5px">
{{ worldImportTable.data.length }} /
{{
worldImportDialog.worldImportFavoriteGroup.capacity -
worldImportDialog.worldImportFavoriteGroup.count
}}
</span>
</div> </div>
<div> <span v-if="worldImportDialog.importProgress" style="margin: 10px">
<Button <Loader2 style="margin-right: 5px" />
size="sm" {{ t('dialog.world_import.import_progress') }}
variant="secondary" {{ worldImportDialog.importProgress }}/{{ worldImportDialog.importProgressTotal }}
class="mr-2" </span>
:disabled="worldImportTable.data.length === 0" <br />
@click="clearWorldImportTable"> <template v-if="worldImportDialog.errors">
{{ t('dialog.world_import.clear_table') }} <Button size="sm" variant="secondary" @click="worldImportDialog.errors = ''">
{{ t('dialog.world_import.clear_errors') }}
</Button> </Button>
<Button <h2 style="font-weight: bold; margin: 5px 0">
size="sm" {{ t('dialog.world_import.errors') }}
:disabled=" </h2>
worldImportTable.data.length === 0 || <pre style="white-space: pre-wrap; font-size: 12px" v-text="worldImportDialog.errors"></pre>
(!worldImportDialog.worldImportFavoriteGroup && </template>
!worldImportDialog.worldImportLocalFavoriteGroup) <DataTableLayout
" class="min-w-0 w-full"
@click="importWorldImportTable"> :table="table"
{{ t('dialog.world_import.import') }} :loading="worldImportDialog.loading"
</Button> :table-style="tableStyle"
</div> :show-pagination="false"
</div> style="margin-top: 10px" />
<span v-if="worldImportDialog.importProgress" style="margin: 10px"> </DialogContent>
<Loader2 style="margin-right: 5px" /> </Dialog>
{{ t('dialog.world_import.import_progress') }}
{{ worldImportDialog.importProgress }}/{{ worldImportDialog.importProgressTotal }}
</span>
<br />
<template v-if="worldImportDialog.errors">
<Button size="sm" variant="secondary" @click="worldImportDialog.errors = ''">
{{ t('dialog.world_import.clear_errors') }}
</Button>
<h2 style="font-weight: bold; margin: 5px 0">
{{ t('dialog.world_import.errors') }}
</h2>
<pre style="white-space: pre-wrap; font-size: 12px" v-text="worldImportDialog.errors"></pre>
</template>
<DataTableLayout
class="min-w-0 w-full"
:table="table"
:loading="worldImportDialog.loading"
:table-style="tableStyle"
:show-pagination="false"
style="margin-top: 10px" />
</el-dialog>
</template> </template>
<script setup> <script setup>
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { DataTableLayout } from '@/components/ui/data-table'; import { DataTableLayout } from '@/components/ui/data-table';
@@ -132,7 +133,6 @@
import { useFavoriteStore, useGalleryStore, useUserStore, useWorldStore } from '../../../stores'; import { useFavoriteStore, useGalleryStore, useUserStore, useWorldStore } from '../../../stores';
import { favoriteRequest, worldRequest } from '../../../api'; import { favoriteRequest, worldRequest } from '../../../api';
import { createColumns } from './worldImportColumns.jsx'; import { createColumns } from './worldImportColumns.jsx';
import { getNextDialogIndex } from '../../../shared/utils/base/ui';
import { removeFromArray } from '../../../shared/utils'; import { removeFromArray } from '../../../shared/utils';
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable'; import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
@@ -147,8 +147,6 @@
const { t } = useI18n(); const { t } = useI18n();
const worldImportDialogIndex = ref(2000);
const worldImportDialog = ref({ const worldImportDialog = ref({
loading: false, loading: false,
progress: 0, progress: 0,
@@ -211,7 +209,6 @@
() => worldImportDialogVisible.value, () => worldImportDialogVisible.value,
(visible) => { (visible) => {
if (visible) { if (visible) {
worldImportDialogIndex.value = getNextDialogIndex();
clearWorldImportTable(); clearWorldImportTable();
resetWorldImport(); resetWorldImport();
if (worldImportDialogInput.value) { if (worldImportDialogInput.value) {

View File

@@ -78,34 +78,37 @@
</div> </div>
</template> </template>
</DataTableLayout> </DataTableLayout>
<el-dialog <Dialog v-model:open="friendsListLoadDialogVisible">
v-model="friendsListLoadDialogVisible" <DialogContent
:title="t('view.friend_list.load_dialog_title')" :show-close-button="false"
width="420px" @interact-outside.prevent
:close-on-click-modal="false" @escape-key-down.prevent
:close-on-press-escape="false" class="sm:max-w-[420px]">
:show-close="false" <DialogHeader>
align-center> <DialogTitle>{{ t('view.friend_list.load_dialog_title') }}</DialogTitle>
<div style="margin-bottom: 10px" v-text="t('view.friend_list.load_dialog_message')"></div> </DialogHeader>
<div class="flex items-center gap-2"> <div style="margin-bottom: 10px" v-text="t('view.friend_list.load_dialog_message')"></div>
<Progress :model-value="friendsListLoadingPercent" class="h-4 w-full" /> <div class="flex items-center gap-2">
<span class="text-xs w-10 text-right">{{ friendsListLoadingPercent }}%</span> <Progress :model-value="friendsListLoadingPercent" class="h-4 w-full" />
</div> <span class="text-xs w-10 text-right">{{ friendsListLoadingPercent }}%</span>
<div style="margin-top: 10px; text-align: right"> </div>
<span>{{ friendsListLoadingCurrent }} / {{ friendsListLoadingTotal }}</span> <div style="margin-top: 10px; text-align: right">
</div> <span>{{ friendsListLoadingCurrent }} / {{ friendsListLoadingTotal }}</span>
<template #footer> </div>
<Button variant="secondary" @click="cancelFriendsListLoad"> <DialogFooter>
{{ t('view.friend_list.load_cancel') }} <Button variant="secondary" @click="cancelFriendsListLoad">
</Button> {{ t('view.friend_list.load_cancel') }}
</template> </Button>
</el-dialog> </DialogFooter>
</DialogContent>
</Dialog>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, nextTick, ref, watch } from 'vue'; import { computed, nextTick, ref, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { InputGroupField } from '@/components/ui/input-group'; import { InputGroupField } from '@/components/ui/input-group';

View File

@@ -1,33 +1,37 @@
<template> <template>
<el-dialog <Dialog
class="x-dialog" :open="editAndSendInviteResponseDialog.visible"
:model-value="editAndSendInviteResponseDialog.visible" @update:open="(open) => (open ? null : cancelEditAndSendInviteResponse())">
@close="cancelEditAndSendInviteResponse" <DialogContent>
:title="t('dialog.edit_send_invite_response_message.header')" <DialogHeader>
width="400px" <DialogTitle>{{ t('dialog.edit_send_invite_response_message.header') }}</DialogTitle>
append-to-body> </DialogHeader>
<div style="font-size: 12px"> <div style="font-size: 12px">
<span>{{ t('dialog.edit_send_invite_response_message.description') }}</span> <span>{{ t('dialog.edit_send_invite_response_message.description') }}</span>
</div> </div>
<InputGroupTextareaField <InputGroupTextareaField
v-model="editAndSendInviteResponseDialog.newMessage" v-model="editAndSendInviteResponseDialog.newMessage"
:maxlength="64" :maxlength="64"
:rows="2" :rows="2"
class="mt-2.5" class="mt-2.5"
placeholder="" placeholder=""
show-count /> show-count />
<template #footer> <DialogFooter>
<Button variant="secondary" class="mr-2" @click="cancelEditAndSendInviteResponse">{{ <Button variant="secondary" class="mr-2" @click="cancelEditAndSendInviteResponse">{{
t('dialog.edit_send_invite_response_message.cancel') t('dialog.edit_send_invite_response_message.cancel')
}}</Button> }}</Button>
<Button @click="saveEditAndSendInviteResponse" :disabled="!editAndSendInviteResponseDialog.newMessage">{{ <Button
t('dialog.edit_send_invite_response_message.send') @click="saveEditAndSendInviteResponse"
}}</Button> :disabled="!editAndSendInviteResponseDialog.newMessage"
</template> >{{ t('dialog.edit_send_invite_response_message.send') }}</Button
</el-dialog> >
</DialogFooter>
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';

View File

@@ -1,44 +1,46 @@
<template> <template>
<el-dialog <Dialog
class="x-dialog" :open="sendInviteRequestResponseDialogVisible"
:model-value="sendInviteRequestResponseDialogVisible" @update:open="(open) => (open ? null : cancelSendInviteRequestResponse())">
:title="t('dialog.invite_request_response_message.header')" <DialogContent>
width="800px" <DialogHeader>
append-to-body <DialogTitle>{{ t('dialog.invite_request_response_message.header') }}</DialogTitle>
@close="cancelSendInviteRequestResponse"> </DialogHeader>
<template v-if="isLocalUserVrcPlusSupporter"> <template v-if="isLocalUserVrcPlusSupporter">
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" /> <input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
</template> </template>
<DataTableLayout <DataTableLayout
style="margin-top: 10px" style="margin-top: 10px"
:table="inviteRequestResponseTable" :table="inviteRequestResponseTable"
:loading="false" :loading="false"
:show-pagination="false" :show-pagination="false"
:on-row-click="handleInviteRequestResponseRowClick" /> :on-row-click="handleInviteRequestResponseRowClick" />
<template #footer> <DialogFooter>
<Button variant="secondary" class="mr-2" @click="cancelSendInviteRequestResponse"> <Button variant="secondary" class="mr-2" @click="cancelSendInviteRequestResponse">
{{ t('dialog.invite_request_response_message.cancel') }} {{ t('dialog.invite_request_response_message.cancel') }}
</Button> </Button>
<Button @click="refreshInviteMessageTableData('requestResponse')"> <Button @click="refreshInviteMessageTableData('requestResponse')">
{{ t('dialog.invite_request_response_message.refresh') }} {{ t('dialog.invite_request_response_message.refresh') }}
</Button> </Button>
</template> </DialogFooter>
<EditAndSendInviteResponseDialog <EditAndSendInviteResponseDialog
:edit-and-send-invite-response-dialog="editAndSendInviteResponseDialog" :edit-and-send-invite-response-dialog="editAndSendInviteResponseDialog"
:send-invite-response-dialog="sendInviteResponseDialog" :send-invite-response-dialog="sendInviteResponseDialog"
@closeInviteDialog="closeInviteDialog" @closeInviteDialog="closeInviteDialog"
@closeResponseConfirmDialog="closeResponseConfirmDialog" /> @closeResponseConfirmDialog="closeResponseConfirmDialog" />
<SendInviteResponseConfirmDialog <SendInviteResponseConfirmDialog
:send-invite-response-dialog="sendInviteResponseDialog" :send-invite-response-dialog="sendInviteResponseDialog"
:send-invite-response-confirm-dialog="sendInviteResponseConfirmDialog" :send-invite-response-confirm-dialog="sendInviteResponseConfirmDialog"
@closeInviteDialog="closeInviteDialog" @closeInviteDialog="closeInviteDialog"
@closeResponseConfirmDialog="closeResponseConfirmDialog" /> @closeResponseConfirmDialog="closeResponseConfirmDialog" />
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { DataTableLayout } from '@/components/ui/data-table'; import { DataTableLayout } from '@/components/ui/data-table';

View File

@@ -1,25 +1,27 @@
<template> <template>
<el-dialog <Dialog
class="x-dialog" :open="sendInviteResponseConfirmDialog.visible"
:model-value="sendInviteResponseConfirmDialog.visible" @update:open="(open) => (open ? null : cancelInviteResponseConfirm())">
:title="t('dialog.invite_response_message.header')" <DialogContent>
width="400px" <DialogHeader>
append-to-body <DialogTitle>{{ t('dialog.invite_response_message.header') }}</DialogTitle>
@close="cancelInviteResponseConfirm"> </DialogHeader>
<div style="font-size: 12px"> <div style="font-size: 12px">
<span>{{ t('dialog.invite_response_message.confirmation') }}</span> <span>{{ t('dialog.invite_response_message.confirmation') }}</span>
</div> </div>
<template #footer> <DialogFooter>
<Button variant="secondary" class="mr-2" @click="cancelInviteResponseConfirm">{{ <Button variant="secondary" class="mr-2" @click="cancelInviteResponseConfirm">{{
t('dialog.invite_response_message.cancel') t('dialog.invite_response_message.cancel')
}}</Button> }}</Button>
<Button @click="sendInviteResponseConfirm">{{ t('dialog.invite_response_message.confirm') }}</Button> <Button @click="sendInviteResponseConfirm">{{ t('dialog.invite_response_message.confirm') }}</Button>
</template> </DialogFooter>
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';

View File

@@ -1,44 +1,44 @@
<template> <template>
<el-dialog <Dialog :open="sendInviteResponseDialogVisible" @update:open="(open) => (open ? null : cancelSendInviteResponse())">
class="x-dialog" <DialogContent>
:model-value="sendInviteResponseDialogVisible" <DialogHeader>
:title="t('dialog.invite_response_message.header')" <DialogTitle>{{ t('dialog.invite_response_message.header') }}</DialogTitle>
width="800px" </DialogHeader>
append-to-body <template v-if="isLocalUserVrcPlusSupporter">
@close="cancelSendInviteResponse"> <input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
<template v-if="isLocalUserVrcPlusSupporter"> </template>
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
</template>
<DataTableLayout <DataTableLayout
style="margin-top: 10px" style="margin-top: 10px"
:table="inviteResponseTable" :table="inviteResponseTable"
:loading="false" :loading="false"
:show-pagination="false" :show-pagination="false"
:on-row-click="handleInviteResponseRowClick" /> :on-row-click="handleInviteResponseRowClick" />
<template #footer> <DialogFooter>
<Button variant="secondary" class="mr-2" @click="cancelSendInviteResponse">{{ <Button variant="secondary" class="mr-2" @click="cancelSendInviteResponse">{{
t('dialog.invite_response_message.cancel') t('dialog.invite_response_message.cancel')
}}</Button> }}</Button>
<Button @click="refreshInviteMessageTableData('response')">{{ <Button @click="refreshInviteMessageTableData('response')">{{
t('dialog.invite_response_message.refresh') t('dialog.invite_response_message.refresh')
}}</Button> }}</Button>
</template> </DialogFooter>
<EditAndSendInviteResponseDialog <EditAndSendInviteResponseDialog
:edit-and-send-invite-response-dialog="editAndSendInviteResponseDialog" :edit-and-send-invite-response-dialog="editAndSendInviteResponseDialog"
:send-invite-response-dialog="sendInviteResponseDialog" :send-invite-response-dialog="sendInviteResponseDialog"
@closeResponseConfirmDialog="closeResponseConfirmDialog" @closeResponseConfirmDialog="closeResponseConfirmDialog"
@closeInviteDialog="closeInviteDialog" /> @closeInviteDialog="closeInviteDialog" />
<SendInviteResponseConfirmDialog <SendInviteResponseConfirmDialog
:send-invite-response-dialog="sendInviteResponseDialog" :send-invite-response-dialog="sendInviteResponseDialog"
:send-invite-response-confirm-dialog="sendInviteResponseConfirmDialog" :send-invite-response-confirm-dialog="sendInviteResponseConfirmDialog"
@closeResponseConfirmDialog="closeResponseConfirmDialog" @closeResponseConfirmDialog="closeResponseConfirmDialog"
@closeInviteDialog="closeInviteDialog" /> @closeInviteDialog="closeInviteDialog" />
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { DataTableLayout } from '@/components/ui/data-table'; import { DataTableLayout } from '@/components/ui/data-table';

View File

@@ -1,60 +1,62 @@
<template> <template>
<el-dialog <Dialog v-model:open="chatboxBlacklistDialog.visible">
class="x-dialog" <DialogContent>
v-model="chatboxBlacklistDialog.visible" <DialogHeader>
:title="t('dialog.chatbox_blacklist.header')" <DialogTitle>{{ t('dialog.chatbox_blacklist.header') }}</DialogTitle>
width="600px"> </DialogHeader>
<div v-if="chatboxBlacklistDialog.visible" v-loading="chatboxBlacklistDialog.loading"> <div v-if="chatboxBlacklistDialog.visible" v-loading="chatboxBlacklistDialog.loading">
<h2>{{ t('dialog.chatbox_blacklist.keyword_blacklist') }}</h2> <h2>{{ t('dialog.chatbox_blacklist.keyword_blacklist') }}</h2>
<InputGroupAction <InputGroupAction
v-for="(item, index) in chatboxBlacklist" v-for="(item, index) in chatboxBlacklist"
:key="index" :key="index"
v-model="chatboxBlacklist[index]" v-model="chatboxBlacklist[index]"
size="sm" size="sm"
style="margin-top: 5px" style="margin-top: 5px"
@change="saveChatboxBlacklist"> @change="saveChatboxBlacklist">
<template #actions> <template #actions>
<Button <Button
variant="outline" variant="outline"
@click=" @click="
chatboxBlacklist.splice(index, 1); chatboxBlacklist.splice(index, 1);
saveChatboxBlacklist(); saveChatboxBlacklist();
"> ">
</Button> </Button>
</template> </template>
</InputGroupAction> </InputGroupAction>
<Button size="sm" variant="outline" style="margin-top: 5px" @click="chatboxBlacklist.push('')"> <Button size="sm" variant="outline" style="margin-top: 5px" @click="chatboxBlacklist.push('')">
{{ t('dialog.chatbox_blacklist.add_item') }} {{ t('dialog.chatbox_blacklist.add_item') }}
</Button> </Button>
<br /> <br />
<h2>{{ t('dialog.chatbox_blacklist.user_blacklist') }}</h2> <h2>{{ t('dialog.chatbox_blacklist.user_blacklist') }}</h2>
<Badge <Badge
v-for="user in chatboxUserBlacklist" v-for="user in chatboxUserBlacklist"
:key="user[0]" :key="user[0]"
variant="outline" variant="outline"
style="margin-right: 5px; margin-top: 5px"> style="margin-right: 5px; margin-top: 5px">
<span>{{ user[1] }}</span> <span>{{ user[1] }}</span>
<button <button
type="button" type="button"
style=" style="
margin-left: 6px; margin-left: 6px;
border: none; border: none;
background: transparent; background: transparent;
padding: 0; padding: 0;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
color: inherit; color: inherit;
cursor: pointer; cursor: pointer;
" "
@click="deleteChatboxUserBlacklist(user[0])"> @click="deleteChatboxUserBlacklist(user[0])">
<X class="h-3 w-3" style="line-height: 1" /> <X class="h-3 w-3" style="line-height: 1" />
</button> </button>
</Badge> </Badge>
</div> </div>
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { InputGroupAction } from '@/components/ui/input-group'; import { InputGroupAction } from '@/components/ui/input-group';
import { X } from 'lucide-vue-next'; import { X } from 'lucide-vue-next';

View File

@@ -1,33 +1,34 @@
<template> <template>
<el-dialog <Dialog :open="isAvatarProviderDialogVisible" @update:open="(open) => (open ? null : closeDialog())">
class="x-dialog" <DialogContent>
:model-value="isAvatarProviderDialogVisible" <DialogHeader>
:title="t('dialog.avatar_database_provider.header')" <DialogTitle>{{ t('dialog.avatar_database_provider.header') }}</DialogTitle>
width="600px" </DialogHeader>
@close="closeDialog"> <div>
<div> <InputGroupAction
<InputGroupAction v-for="(provider, index) in avatarRemoteDatabaseProviderList"
v-for="(provider, index) in avatarRemoteDatabaseProviderList" :key="index"
:key="index" v-model="avatarRemoteDatabaseProviderList[index]"
v-model="avatarRemoteDatabaseProviderList[index]" size="sm"
size="sm" style="margin-top: 5px"
style="margin-top: 5px" @change="saveAvatarProviderList">
@change="saveAvatarProviderList"> <template #actions>
<template #actions> <Button variant="outline" size="icon" @click="removeAvatarProvider(provider)">
<Button variant="outline" size="icon" @click="removeAvatarProvider(provider)"> <Trash2 />
<Trash2 /> </Button>
</Button> </template>
</template> </InputGroupAction>
</InputGroupAction>
<Button size="sm" style="margin-top: 5px" @click="avatarRemoteDatabaseProviderList.push('')"> <Button size="sm" style="margin-top: 5px" @click="avatarRemoteDatabaseProviderList.push('')">
{{ t('dialog.avatar_database_provider.add_provider') }} {{ t('dialog.avatar_database_provider.add_provider') }}
</Button> </Button>
</div> </div>
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { InputGroupAction } from '@/components/ui/input-group'; import { InputGroupAction } from '@/components/ui/input-group';
import { Trash2 } from 'lucide-vue-next'; import { Trash2 } from 'lucide-vue-next';

View File

@@ -1,43 +1,43 @@
<template> <template>
<el-dialog <Dialog v-model:open="changeLogDialog.visible">
class="x-dialog" <DialogContent class="sm:max-w-4xl">
:model-value="changeLogDialog.visible" <DialogHeader>
:title="t('dialog.change_log.header')" <DialogTitle>{{ t('dialog.change_log.header') }}</DialogTitle>
width="800px" </DialogHeader>
append-to-body <div v-loading="!changeLogDialog.changeLog" class="changelog-dialog">
@close="closeDialog"> <h2 v-text="changeLogDialog.buildName"></h2>
<div v-loading="!changeLogDialog.changeLog" class="changelog-dialog"> <span v-show="changeLogDialog.buildName">
<h2 v-text="changeLogDialog.buildName"></h2> {{ t('dialog.change_log.description') }}
<span v-show="changeLogDialog.buildName"> <a class="x-link" @click="openExternalLink('https://www.patreon.com/Natsumi_VRCX')">Patreon</a>,
{{ t('dialog.change_log.description') }} <a class="x-link" @click="openExternalLink('https://ko-fi.com/natsumi_sama')">Ko-fi</a>.
<a class="x-link" @click="openExternalLink('https://www.patreon.com/Natsumi_VRCX')">Patreon</a>, </span>
<a class="x-link" @click="openExternalLink('https://ko-fi.com/natsumi_sama')">Ko-fi</a>. <VueShowdown
</span> :markdown="changeLogDialog.changeLog"
<VueShowdown flavor="github"
:markdown="changeLogDialog.changeLog" :options="showdownOptions"
flavor="github" @click="handleLinkClick"
:options="showdownOptions" style="height: 62vh; overflow-y: auto; margin-top: 10px" />
@click="handleLinkClick" </div>
style="height: 62vh; overflow-y: auto; margin-top: 10px" /> <DialogFooter>
</div> <Button
<template #footer> variant="ghost"
<Button class="mr-2"
variant="ghost" @click="openExternalLink('https://github.com/vrcx-team/VRCX/releases')">
class="mr-2" {{ t('dialog.change_log.github') }}
@click="openExternalLink('https://github.com/vrcx-team/VRCX/releases')"> </Button>
{{ t('dialog.change_log.github') }} <Button variant="outline" class="mr-2" @click="openExternalLink('https://patreon.com/Natsumi_VRCX')">
</Button> {{ t('dialog.change_log.donate') }}
<Button variant="outline" class="mr-2" @click="openExternalLink('https://patreon.com/Natsumi_VRCX')"> </Button>
{{ t('dialog.change_log.donate') }} <Button @click="closeDialog">
</Button> {{ t('dialog.change_log.close') }}
<Button @click="closeDialog"> </Button>
{{ t('dialog.change_log.close') }} </DialogFooter>
</Button> </DialogContent>
</template> </Dialog>
</el-dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { defineAsyncComponent } from 'vue'; import { defineAsyncComponent } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';

View File

@@ -1,49 +1,23 @@
<template> <template>
<el-dialog <Dialog :open="!!feedFiltersDialogMode" @update:open="(open) => !open && handleDialogClose()">
:model-value="!!feedFiltersDialogMode" <DialogContent>
:title="dialogTitle" <DialogHeader>
width="600px" <DialogTitle>{{ dialogTitle }}</DialogTitle>
destroy-on-close </DialogHeader>
@close="handleDialogClose"> <div class="toggle-list" style="height: 75vh; overflow-y: auto">
<div class="toggle-list" style="height: 75vh; overflow-y: auto"> <div v-for="setting in currentOptions" :key="setting.key" class="toggle-item">
<div v-for="setting in currentOptions" :key="setting.key" class="toggle-item"> <span class="toggle-name"
<span class="toggle-name" >{{ setting.name
>{{ setting.name }}<TooltipWrapper
}}<TooltipWrapper v-if="setting.tooltip"
v-if="setting.tooltip" side="top"
side="top" style="margin-left: 5px"
style="margin-left: 5px" :content="setting.tooltip">
:content="setting.tooltip"> <AlertTriangle v-if="setting.tooltipWarning" />
<AlertTriangle v-if="setting.tooltipWarning" /> <Info v-else />
<Info v-else /> </TooltipWrapper>
</TooltipWrapper> </span>
</span>
<ToggleGroup
type="single"
required
variant="outline"
size="sm"
:model-value="currentSharedFeedFilters[setting.key]"
@update:model-value="
(value) => {
currentSharedFeedFilters[setting.key] = value;
saveSharedFeedFilters();
}
">
<ToggleGroupItem v-for="option in setting.options" :key="option.label" :value="option.label">
{{ t(option.textKey) }}
</ToggleGroupItem>
</ToggleGroup>
</div>
<template v-if="photonLoggingEnabled">
<br />
<div class="toggle-item">
<span class="toggle-name">Photon Event Logging</span>
</div>
<div v-for="setting in photonFeedFiltersOptions" :key="setting.key" class="toggle-item">
<span class="toggle-name">{{ setting.name }}</span>
<ToggleGroup <ToggleGroup
type="single" type="single"
required required
@@ -61,22 +35,52 @@
</ToggleGroupItem> </ToggleGroupItem>
</ToggleGroup> </ToggleGroup>
</div> </div>
</template>
</div>
<template #footer> <template v-if="photonLoggingEnabled">
<Button variant="secondary" @click="currentResetFunction">{{ <br />
t('dialog.shared_feed_filters.reset') <div class="toggle-item">
}}</Button> <span class="toggle-name">Photon Event Logging</span>
<Button style="margin-left: 10px" @click="handleDialogClose">{{ </div>
t('dialog.shared_feed_filters.close') <div v-for="setting in photonFeedFiltersOptions" :key="setting.key" class="toggle-item">
}}</Button> <span class="toggle-name">{{ setting.name }}</span>
</template> <ToggleGroup
</el-dialog> type="single"
required
variant="outline"
size="sm"
:model-value="currentSharedFeedFilters[setting.key]"
@update:model-value="
(value) => {
currentSharedFeedFilters[setting.key] = value;
saveSharedFeedFilters();
}
">
<ToggleGroupItem
v-for="option in setting.options"
:key="option.label"
:value="option.label">
{{ t(option.textKey) }}
</ToggleGroupItem>
</ToggleGroup>
</div>
</template>
</div>
<DialogFooter>
<Button variant="secondary" @click="currentResetFunction">{{
t('dialog.shared_feed_filters.reset')
}}</Button>
<Button style="margin-left: 10px" @click="handleDialogClose">{{
t('dialog.shared_feed_filters.close')
}}</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Info, AlertTriangle } from 'lucide-vue-next'; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { AlertTriangle, Info } from 'lucide-vue-next';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { computed } from 'vue'; import { computed } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';

View File

@@ -1,63 +1,64 @@
<template> <template>
<el-dialog <Dialog :open="isLaunchOptionsDialogVisible" @update:open="(open) => (open ? null : closeDialog())">
class="x-dialog" <DialogContent>
:model-value="isLaunchOptionsDialogVisible" <DialogHeader>
:title="t('dialog.launch_options.header')" <DialogTitle>{{ t('dialog.launch_options.header') }}</DialogTitle>
width="600px" </DialogHeader>
@close="closeDialog"> <div style="font-size: 12px">
<div style="font-size: 12px"> {{ t('dialog.launch_options.description') }} <br />
{{ t('dialog.launch_options.description') }} <br /> {{ t('dialog.launch_options.example') }}
{{ t('dialog.launch_options.example') }} <Badge variant="outline"
<Badge variant="outline" >--fps=144 --enable-debug-gui --enable-sdk-log-levels --enable-udon-debug-logging
>--fps=144 --enable-debug-gui --enable-sdk-log-levels --enable-udon-debug-logging </Badge>
</Badge>
</div>
<InputGroupTextareaField
v-model="launchOptionsDialog.launchArguments"
:autosize="{ minRows: 2, maxRows: 5 }"
:rows="2"
placeholder=""
style="margin-top: 10px"
input-class="resize-none" />
<template v-if="!isLinux">
<div style="font-size: 12px; margin-top: 10px">
{{ t('dialog.launch_options.path_override') }}
</div> </div>
<InputGroupTextareaField <InputGroupTextareaField
v-model="launchOptionsDialog.vrcLaunchPathOverride" v-model="launchOptionsDialog.launchArguments"
placeholder="C:\Program Files (x86)\Steam\steamapps\common\VRChat" :autosize="{ minRows: 2, maxRows: 5 }"
:rows="1" :rows="2"
style="display: block; margin-top: 10px" placeholder=""
input-class="resize-none min-h-0" /> style="margin-top: 10px"
</template> input-class="resize-none" />
<template #footer> <template v-if="!isLinux">
<div class="flex items-center justify-between"> <div style="font-size: 12px; margin-top: 10px">
<div> {{ t('dialog.launch_options.path_override') }}
<Button </div>
variant="outline"
class="mr-2" <InputGroupTextareaField
@click="openExternalLink('https://docs.vrchat.com/docs/launch-options')"> v-model="launchOptionsDialog.vrcLaunchPathOverride"
{{ t('dialog.launch_options.vrchat_docs') }} placeholder="C:\Program Files (x86)\Steam\steamapps\common\VRChat"
</Button> :rows="1"
<Button style="display: block; margin-top: 10px"
variant="outline" input-class="resize-none min-h-0" />
@click="openExternalLink('https://docs.unity3d.com/Manual/CommandLineArguments.html')"> </template>
{{ t('dialog.launch_options.unity_manual') }}
<DialogFooter>
<div class="flex items-center justify-between">
<div>
<Button
variant="outline"
class="mr-2"
@click="openExternalLink('https://docs.vrchat.com/docs/launch-options')">
{{ t('dialog.launch_options.vrchat_docs') }}
</Button>
<Button
variant="outline"
@click="openExternalLink('https://docs.unity3d.com/Manual/CommandLineArguments.html')">
{{ t('dialog.launch_options.unity_manual') }}
</Button>
</div>
<Button @click="updateLaunchOptions">
{{ t('dialog.launch_options.save') }}
</Button> </Button>
</div> </div>
<Button @click="updateLaunchOptions"> </DialogFooter>
{{ t('dialog.launch_options.save') }} </DialogContent>
</Button> </Dialog>
</div>
</template>
</el-dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';

View File

@@ -1,92 +1,93 @@
<template> <template>
<el-dialog <Dialog :open="isNotificationPositionDialogVisible" @update:open="(open) => (open ? null : closeDialog())">
class="x-dialog" <DialogContent>
:model-value="isNotificationPositionDialogVisible" <DialogHeader>
:title="t('dialog.notification_position.header')" <DialogTitle>{{ t('dialog.notification_position.header') }}</DialogTitle>
width="400px" </DialogHeader>
@close="closeDialog"> <div style="font-size: 12px">
<div style="font-size: 12px"> {{ t('dialog.notification_position.description') }}
{{ t('dialog.notification_position.description') }}
</div>
<div class="relative mx-auto mt-4 size-75">
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="80 80 80 100"
style="margin-top: 24px"
xml:space="preserve"
class="absolute inset-0 size-full">
<path
style="fill: black"
d="M291.89,5A3.11,3.11,0,0,1,295,8.11V160.64a3.11,3.11,0,0,1-3.11,3.11H8.11A3.11,3.11,0,0,1,5,160.64V8.11A3.11,3.11,0,0,1,8.11,5H291.89m0-5H8.11A8.11,8.11,0,0,0,0,8.11V160.64a8.11,8.11,0,0,0,8.11,8.11H291.89a8.11,8.11,0,0,0,8.11-8.11V8.11A8.11,8.11,0,0,0,291.89,0Z" />
<rect style="fill: #c4c4c4" x="5" y="5" width="290" height="158.75" rx="2.5" />
</svg>
<RadioGroup
:model-value="notificationPosition"
class="absolute inset-0"
@update:modelValue="changeNotificationPosition">
<RadioGroupItem
id="notificationPosition-topLeft"
aria-label="topLeft"
value="topLeft"
class="absolute top-[20%] left-[10%] -translate-x-1/2 -translate-y-1/2" />
<RadioGroupItem
id="notificationPosition-top"
aria-label="top"
value="top"
class="absolute top-[20%] left-1/2 -translate-x-1/2 -translate-y-1/2" />
<RadioGroupItem
id="notificationPosition-topRight"
aria-label="topRight"
value="topRight"
class="absolute top-[20%] left-[90%] -translate-x-1/2 -translate-y-1/2" />
<RadioGroupItem
id="notificationPosition-centerLeft"
aria-label="centerLeft"
value="centerLeft"
class="absolute top-1/2 left-[10%] -translate-x-1/2 -translate-y-1/2" />
<RadioGroupItem
id="notificationPosition-center"
aria-label="center"
value="center"
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2" />
<RadioGroupItem
id="notificationPosition-centerRight"
aria-label="centerRight"
value="centerRight"
class="absolute top-1/2 left-[90%] -translate-x-1/2 -translate-y-1/2" />
<RadioGroupItem
id="notificationPosition-bottomLeft"
aria-label="bottomLeft"
value="bottomLeft"
class="absolute top-[80%] left-[10%] -translate-x-1/2 -translate-y-1/2" />
<RadioGroupItem
id="notificationPosition-bottom"
aria-label="bottom"
value="bottom"
class="absolute top-[80%] left-1/2 -translate-x-1/2 -translate-y-1/2" />
<RadioGroupItem
id="notificationPosition-bottomRight"
aria-label="bottomRight"
value="bottomRight"
class="absolute top-[80%] left-[90%] -translate-x-1/2 -translate-y-1/2" />
</RadioGroup>
</div>
<template #footer>
<div style="display: flex">
<Button @click="closeDialog">
{{ t('dialog.notification_position.ok') }}
</Button>
</div> </div>
</template> <div class="relative mx-auto mt-4 size-75">
</el-dialog> <svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="80 80 80 100"
style="margin-top: 24px"
xml:space="preserve"
class="absolute inset-0 size-full">
<path
style="fill: black"
d="M291.89,5A3.11,3.11,0,0,1,295,8.11V160.64a3.11,3.11,0,0,1-3.11,3.11H8.11A3.11,3.11,0,0,1,5,160.64V8.11A3.11,3.11,0,0,1,8.11,5H291.89m0-5H8.11A8.11,8.11,0,0,0,0,8.11V160.64a8.11,8.11,0,0,0,8.11,8.11H291.89a8.11,8.11,0,0,0,8.11-8.11V8.11A8.11,8.11,0,0,0,291.89,0Z" />
<rect style="fill: #c4c4c4" x="5" y="5" width="290" height="158.75" rx="2.5" />
</svg>
<RadioGroup
:model-value="notificationPosition"
class="absolute inset-0"
@update:modelValue="changeNotificationPosition">
<RadioGroupItem
id="notificationPosition-topLeft"
aria-label="topLeft"
value="topLeft"
class="absolute top-[20%] left-[10%] -translate-x-1/2 -translate-y-1/2" />
<RadioGroupItem
id="notificationPosition-top"
aria-label="top"
value="top"
class="absolute top-[20%] left-1/2 -translate-x-1/2 -translate-y-1/2" />
<RadioGroupItem
id="notificationPosition-topRight"
aria-label="topRight"
value="topRight"
class="absolute top-[20%] left-[90%] -translate-x-1/2 -translate-y-1/2" />
<RadioGroupItem
id="notificationPosition-centerLeft"
aria-label="centerLeft"
value="centerLeft"
class="absolute top-1/2 left-[10%] -translate-x-1/2 -translate-y-1/2" />
<RadioGroupItem
id="notificationPosition-center"
aria-label="center"
value="center"
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2" />
<RadioGroupItem
id="notificationPosition-centerRight"
aria-label="centerRight"
value="centerRight"
class="absolute top-1/2 left-[90%] -translate-x-1/2 -translate-y-1/2" />
<RadioGroupItem
id="notificationPosition-bottomLeft"
aria-label="bottomLeft"
value="bottomLeft"
class="absolute top-[80%] left-[10%] -translate-x-1/2 -translate-y-1/2" />
<RadioGroupItem
id="notificationPosition-bottom"
aria-label="bottom"
value="bottom"
class="absolute top-[80%] left-1/2 -translate-x-1/2 -translate-y-1/2" />
<RadioGroupItem
id="notificationPosition-bottomRight"
aria-label="bottomRight"
value="bottomRight"
class="absolute top-[80%] left-[90%] -translate-x-1/2 -translate-y-1/2" />
</RadioGroup>
</div>
<DialogFooter>
<div style="display: flex">
<Button @click="closeDialog">
{{ t('dialog.notification_position.ok') }}
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';

View File

@@ -1,25 +1,25 @@
<template> <template>
<el-dialog <Dialog :open="ossDialog" @update:open="(open) => !open && closeDialog()">
class="x-dialog" <DialogContent>
:model-value="ossDialog" <DialogHeader>
:title="t('dialog.open_source.header')" <DialogTitle>{{ t('dialog.open_source.header') }}</DialogTitle>
width="650px" </DialogHeader>
@close="closeDialog" <div v-once style="height: 350px; overflow: hidden scroll; word-break: break-all">
destroy-on-close> <div>
<div v-once style="height: 350px; overflow: hidden scroll; word-break: break-all"> <span>{{ t('dialog.open_source.description') }}</span>
<div> </div>
<span>{{ t('dialog.open_source.description') }}</span>
</div>
<div v-for="lib in openSourceSoftwareLicenses" :key="lib.name" style="margin-top: 15px"> <div v-for="lib in openSourceSoftwareLicenses" :key="lib.name" style="margin-top: 15px">
<p style="font-weight: bold">{{ lib.name }}</p> <p style="font-weight: bold">{{ lib.name }}</p>
<pre style="font-size: 12px; white-space: pre-line">{{ lib.licenseText }}</pre> <pre style="font-size: 12px; white-space: pre-line">{{ lib.licenseText }}</pre>
</div>
</div> </div>
</div> </DialogContent>
</el-dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { openSourceSoftwareLicenses } from '../../../shared/constants/ossLicenses'; import { openSourceSoftwareLicenses } from '../../../shared/constants/ossLicenses';

View File

@@ -1,41 +1,41 @@
<template> <template>
<el-dialog <Dialog v-model:open="enablePrimaryPasswordDialog.visible">
class="x-dialog" <DialogContent @interact-outside.prevent>
v-model="enablePrimaryPasswordDialog.visible" <DialogHeader>
:before-close="enablePrimaryPasswordDialog.beforeClose" <DialogTitle>{{ t('dialog.primary_password.header') }}</DialogTitle>
:close-on-click-modal="false" </DialogHeader>
:title="t('dialog.primary_password.header')" <InputGroupField
width="400px"> v-model="enablePrimaryPasswordDialog.password"
<InputGroupField :placeholder="t('dialog.primary_password.password_placeholder')"
v-model="enablePrimaryPasswordDialog.password" type="password"
:placeholder="t('dialog.primary_password.password_placeholder')" size="sm"
type="password" maxlength="32"
size="sm" show-password
maxlength="32" autofocus />
show-password <InputGroupField
autofocus /> v-model="enablePrimaryPasswordDialog.rePassword"
<InputGroupField :placeholder="t('dialog.primary_password.re_input_placeholder')"
v-model="enablePrimaryPasswordDialog.rePassword" type="password"
:placeholder="t('dialog.primary_password.re_input_placeholder')" style="margin-top: 5px"
type="password" size="sm"
style="margin-top: 5px" maxlength="32"
size="sm" show-password />
maxlength="32" <DialogFooter>
show-password /> <Button
<template #footer> :disabled="
<Button enablePrimaryPasswordDialog.password.length === 0 ||
:disabled=" enablePrimaryPasswordDialog.password !== enablePrimaryPasswordDialog.rePassword
enablePrimaryPasswordDialog.password.length === 0 || "
enablePrimaryPasswordDialog.password !== enablePrimaryPasswordDialog.rePassword @click="handleSetPrimaryPassword()">
" {{ t('dialog.primary_password.ok') }}
@click="handleSetPrimaryPassword()"> </Button>
{{ t('dialog.primary_password.ok') }} </DialogFooter>
</Button> </DialogContent>
</template> </Dialog>
</el-dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { InputGroupField } from '@/components/ui/input-group'; import { InputGroupField } from '@/components/ui/input-group';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';

View File

@@ -1,52 +1,54 @@
<template> <template>
<el-dialog <Dialog :open="isRegistryBackupDialogVisible" @update:open="(open) => !open && closeAndClearDialog()">
class="x-dialog" <DialogContent>
:model-value="isRegistryBackupDialogVisible" <DialogHeader>
:title="t('dialog.registry_backup.header')" <DialogTitle>{{ t('dialog.registry_backup.header') }}</DialogTitle>
width="600px" </DialogHeader>
@close="closeDialog" <div style="margin-top: 10px">
@closed="clearVrcRegistryDialog"> <div style="display: flex; align-items: center; justify-content: space-between; font-size: 12px">
<div style="margin-top: 10px"> <span class="name" style="margin-right: 24px">{{ t('dialog.registry_backup.auto_backup') }}</span>
<div style="display: flex; align-items: center; justify-content: space-between; font-size: 12px"> <Switch :model-value="vrcRegistryAutoBackup" @update:modelValue="setVrcRegistryAutoBackup" />
<span class="name" style="margin-right: 24px">{{ t('dialog.registry_backup.auto_backup') }}</span> </div>
<Switch :model-value="vrcRegistryAutoBackup" @update:modelValue="setVrcRegistryAutoBackup" /> <div
</div> style="
<div display: flex;
style=" align-items: center;
display: flex; justify-content: space-between;
align-items: center; font-size: 12px;
justify-content: space-between; margin-top: 5px;
font-size: 12px; ">
margin-top: 5px; <span class="name" style="margin-right: 24px">{{
"> t('dialog.registry_backup.ask_to_restore')
<span class="name" style="margin-right: 24px">{{ t('dialog.registry_backup.ask_to_restore') }}</span> }}</span>
<Switch :model-value="vrcRegistryAskRestore" @update:modelValue="setVrcRegistryAskRestore" /> <Switch :model-value="vrcRegistryAskRestore" @update:modelValue="setVrcRegistryAskRestore" />
</div> </div>
<DataTableLayout <DataTableLayout
class="min-w-0 w-full" class="min-w-0 w-full"
:table="table" :table="table"
:loading="false" :loading="false"
:table-style="tableStyle" :table-style="tableStyle"
:show-pagination="false" :show-pagination="false"
style="margin-top: 10px" /> style="margin-top: 10px" />
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 10px"> <div style="display: flex; align-items: center; justify-content: space-between; margin-top: 10px">
<Button size="sm" variant="destructive" @click="deleteVrcRegistry">{{ <Button size="sm" variant="destructive" @click="deleteVrcRegistry">{{
t('dialog.registry_backup.reset') t('dialog.registry_backup.reset')
}}</Button>
<div class="flex gap-2">
<Button size="sm" variant="outline" @click="promptVrcRegistryBackupName">{{
t('dialog.registry_backup.backup')
}}</Button>
<Button size="sm" variant="outline" @click="restoreVrcRegistryFromFile">{{
t('dialog.registry_backup.restore_from_file')
}}</Button> }}</Button>
<div class="flex gap-2">
<Button size="sm" variant="outline" @click="promptVrcRegistryBackupName">{{
t('dialog.registry_backup.backup')
}}</Button>
<Button size="sm" variant="outline" @click="restoreVrcRegistryFromFile">{{
t('dialog.registry_backup.restore_from_file')
}}</Button>
</div>
</div> </div>
</div> </div>
</div> </DialogContent>
</el-dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { DataTableLayout } from '@/components/ui/data-table'; import { DataTableLayout } from '@/components/ui/data-table';
@@ -201,7 +203,8 @@
document.body.appendChild(fileInput); document.body.appendChild(fileInput);
fileInput.onchange = function (event) { fileInput.onchange = function (event) {
const file = event.target.files[0]; const target = /** @type {HTMLInputElement | null} */ (event.target);
const file = target?.files?.[0];
if (file) { if (file) {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = function () { reader.onload = function () {
@@ -263,6 +266,13 @@
registryBackupTable.value.data = []; registryBackupTable.value.data = [];
} }
function closeAndClearDialog() {
closeDialog();
// TODO: Element Plus had a distinct @closed event after animation.
// If you ever need exact timing, wrap DialogContent with a Transition and call clear on after-leave.
clearVrcRegistryDialog();
}
function closeDialog() { function closeDialog() {
isRegistryBackupDialogVisible.value = false; isRegistryBackupDialogVisible.value = false;
} }

View File

@@ -1,136 +1,139 @@
<template> <template>
<el-dialog <Dialog :open="isTranslationApiDialogVisible" @update:open="(open) => (open ? null : closeDialog())">
class="x-dialog" <DialogContent>
:model-value="isTranslationApiDialogVisible" <DialogHeader>
:title="t('dialog.translation_api.header')" <DialogTitle>{{ t('dialog.translation_api.header') }}</DialogTitle>
width="450px" </DialogHeader>
@close="closeDialog"> <div class="options-container-item">
<div class="options-container-item"> <span class="name">{{ t('view.settings.appearance.appearance.bio_language') }}</span>
<span class="name">{{ t('view.settings.appearance.appearance.bio_language') }}</span> <Select :model-value="bioLanguage" @update:modelValue="setBioLanguage">
<Select :model-value="bioLanguage" @update:modelValue="setBioLanguage"> <SelectTrigger size="sm" style="float: right">
<SelectTrigger size="sm" style="float: right"> <SelectValue :placeholder="String(getLanguageName(bioLanguage) || bioLanguage || '')" />
<SelectValue :placeholder="String(getLanguageName(bioLanguage) || bioLanguage || '')" /> </SelectTrigger>
</SelectTrigger> <SelectContent>
<SelectContent> <SelectGroup>
<SelectGroup> <SelectItem v-for="language in languageCodes" :key="language" :value="language">
<SelectItem v-for="language in languageCodes" :key="language" :value="language"> {{ getLanguageName(language) }}
{{ getLanguageName(language) }} </SelectItem>
</SelectItem> </SelectGroup>
</SelectGroup> </SelectContent>
</SelectContent> </Select>
</Select>
</div>
<br />
<FieldGroup class="mb-3">
<Field>
<FieldLabel>{{ t('dialog.translation_api.mode') }}</FieldLabel>
<FieldContent>
<Select :model-value="form.translationApiType" @update:modelValue="handleTranslationApiTypeChange">
<SelectTrigger size="sm" style="width: 100%">
<SelectValue :placeholder="t('dialog.translation_api.mode')" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="google" :text-value="t('dialog.translation_api.mode_google')">
{{ t('dialog.translation_api.mode_google') }}
</SelectItem>
<SelectItem value="openai" :text-value="t('dialog.translation_api.mode_openai')">
{{ t('dialog.translation_api.mode_openai') }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</FieldContent>
</Field>
</FieldGroup>
<template v-if="form.translationApiType === 'google'">
<FieldGroup>
<Field>
<FieldLabel>{{ t('dialog.translation_api.description') }}</FieldLabel>
<FieldContent>
<InputGroupField
v-model="form.translationApiKey"
type="password"
show-password
placeholder="AIzaSy..."
clearable />
</FieldContent>
</Field>
</FieldGroup>
</template>
<template v-if="form.translationApiType === 'openai'">
<FieldGroup>
<Field>
<FieldLabel>{{ t('dialog.translation_api.openai.endpoint') }}</FieldLabel>
<FieldContent>
<InputGroupField
v-model="form.translationApiEndpoint"
placeholder="https://api.openai.com/v1/chat/completions"
clearable />
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('dialog.translation_api.openai.api_key') }}</FieldLabel>
<FieldContent>
<InputGroupField
v-model="form.translationApiKey"
type="password"
show-password
placeholder="sk-..."
clearable />
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('dialog.translation_api.openai.model') }}</FieldLabel>
<FieldContent>
<InputGroupField v-model="form.translationApiModel" clearable />
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('dialog.translation_api.openai.prompt_optional') }}</FieldLabel>
<FieldContent>
<InputGroupTextareaField v-model="form.translationApiPrompt" :rows="3" clearable />
</FieldContent>
</Field>
</FieldGroup>
</template>
<template #footer>
<div class="flex items-center justify-between">
<Button
variant="outline"
v-if="form.translationApiType === 'google'"
@click="
openExternalLink(
'https://translatepress.com/docs/automatic-translation/generate-google-api-key/'
)
">
{{ t('dialog.translation_api.guide') }}
</Button>
<Button
variant="outline"
class="mr-2"
v-if="form.translationApiType === 'openai'"
@click="testOpenAiTranslation">
{{ t('dialog.translation_api.test') }}
</Button>
<div>
<Button style="margin-left: auto" @click="saveTranslationApiConfig">
{{ t('dialog.translation_api.save') }}
</Button>
</div>
</div> </div>
</template> <br />
</el-dialog> <FieldGroup class="mb-3">
<Field>
<FieldLabel>{{ t('dialog.translation_api.mode') }}</FieldLabel>
<FieldContent>
<Select
:model-value="form.translationApiType"
@update:modelValue="handleTranslationApiTypeChange">
<SelectTrigger size="sm" style="width: 100%">
<SelectValue :placeholder="t('dialog.translation_api.mode')" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="google" :text-value="t('dialog.translation_api.mode_google')">
{{ t('dialog.translation_api.mode_google') }}
</SelectItem>
<SelectItem value="openai" :text-value="t('dialog.translation_api.mode_openai')">
{{ t('dialog.translation_api.mode_openai') }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</FieldContent>
</Field>
</FieldGroup>
<template v-if="form.translationApiType === 'google'">
<FieldGroup>
<Field>
<FieldLabel>{{ t('dialog.translation_api.description') }}</FieldLabel>
<FieldContent>
<InputGroupField
v-model="form.translationApiKey"
type="password"
show-password
placeholder="AIzaSy..."
clearable />
</FieldContent>
</Field>
</FieldGroup>
</template>
<template v-if="form.translationApiType === 'openai'">
<FieldGroup>
<Field>
<FieldLabel>{{ t('dialog.translation_api.openai.endpoint') }}</FieldLabel>
<FieldContent>
<InputGroupField
v-model="form.translationApiEndpoint"
placeholder="https://api.openai.com/v1/chat/completions"
clearable />
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('dialog.translation_api.openai.api_key') }}</FieldLabel>
<FieldContent>
<InputGroupField
v-model="form.translationApiKey"
type="password"
show-password
placeholder="sk-..."
clearable />
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('dialog.translation_api.openai.model') }}</FieldLabel>
<FieldContent>
<InputGroupField v-model="form.translationApiModel" clearable />
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('dialog.translation_api.openai.prompt_optional') }}</FieldLabel>
<FieldContent>
<InputGroupTextareaField v-model="form.translationApiPrompt" :rows="3" clearable />
</FieldContent>
</Field>
</FieldGroup>
</template>
<DialogFooter>
<div class="flex items-center justify-between">
<Button
variant="outline"
v-if="form.translationApiType === 'google'"
@click="
openExternalLink(
'https://translatepress.com/docs/automatic-translation/generate-google-api-key/'
)
">
{{ t('dialog.translation_api.guide') }}
</Button>
<Button
variant="outline"
class="mr-2"
v-if="form.translationApiType === 'openai'"
@click="testOpenAiTranslation">
{{ t('dialog.translation_api.test') }}
</Button>
<div>
<Button style="margin-left: auto" @click="saveTranslationApiConfig">
{{ t('dialog.translation_api.save') }}
</Button>
</div>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field'; import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field';
import { InputGroupField, InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupField, InputGroupTextareaField } from '@/components/ui/input-group';
import { reactive, watch } from 'vue'; import { reactive, watch } from 'vue';

View File

@@ -1,176 +1,181 @@
<template> <template>
<el-dialog <Dialog :open="isVRChatConfigDialogVisible" @update:open="(open) => (open ? null : closeDialog())">
class="x-dialog" <DialogContent>
:model-value="isVRChatConfigDialogVisible" <DialogHeader>
:title="t('dialog.config_json.header')" <DialogTitle>{{ t('dialog.config_json.header') }}</DialogTitle>
width="420px" </DialogHeader>
@close="closeDialog"> <div v-loading="loading">
<div v-loading="loading"> <div style="font-size: 12px; word-break: keep-all">
<div style="font-size: 12px; word-break: keep-all"> {{ t('dialog.config_json.description1') }} <br />
{{ t('dialog.config_json.description1') }} <br /> {{ t('dialog.config_json.description2') }}
{{ t('dialog.config_json.description2') }}
</div>
<br />
<span style="margin-right: 5px">{{ t('dialog.config_json.cache_size') }}</span>
<span v-text="VRChatUsedCacheSize"></span>
<span>/</span>
<span v-text="totalCacheSize"></span>
<span>GB</span>
<TooltipWrapper side="top" :content="t('dialog.config_json.refresh')">
<Button
class="rounded-full"
variant="outline"
size="icon-sm"
:disabled="VRChatCacheSizeLoading"
style="margin-left: 5px"
@click="getVRChatCacheSize">
<Spinner v-if="VRChatCacheSizeLoading" />
<RefreshCw v-else />
</Button>
</TooltipWrapper>
<div style="margin-top: 10px">
<span style="margin-right: 5px">{{ t('dialog.config_json.delete_all_cache') }}</span>
<Button size="sm" variant="outline" style="margin-left: 5px" @click="showDeleteAllVRChatCacheConfirm">{{
t('dialog.config_json.delete_cache')
}}</Button>
</div>
<div style="margin-top: 10px">
<span style="margin-right: 5px">{{ t('dialog.config_json.delete_old_cache') }}</span>
<Button size="sm" variant="outline" style="margin-left: 5px" @click="sweepVRChatCache">{{
t('dialog.config_json.sweep_cache')
}}</Button>
</div>
<div v-for="(item, value) in VRChatConfigList" :key="value" style="display: block; margin-top: 10px">
<span style="word-break: keep-all">{{ item.name }}:</span>
<div style="display: flex">
<InputGroupAction
v-model="VRChatConfigFile[value]"
:placeholder="item.default"
size="sm"
:type="item.type ? item.type : 'text'"
:min="item.min"
:max="item.max"
@input="refreshDialogValues"
style="flex: 1; margin-top: 5px">
<template #actions>
<Button
size="sm"
variant="outline"
v-if="item.folderBrowser"
@click="openConfigFolderBrowser(value)">
</Button>
</template>
</InputGroupAction>
</div> </div>
</div>
<div style="display: inline-block; margin-top: 10px">
<span>{{ t('dialog.config_json.camera_resolution') }}</span>
<br /> <br />
<Select <span style="margin-right: 5px">{{ t('dialog.config_json.cache_size') }}</span>
:model-value="vrchatCameraResolutionKey" <span v-text="VRChatUsedCacheSize"></span>
@update:modelValue="(v) => (vrchatCameraResolutionKey = v)"> <span>/</span>
<SelectTrigger size="sm" style="margin-top: 5px"> <span v-text="totalCacheSize"></span>
<SelectValue :placeholder="getVRChatCameraResolution()" /> <span>GB</span>
</SelectTrigger> <TooltipWrapper side="top" :content="t('dialog.config_json.refresh')">
<SelectContent>
<SelectGroup>
<SelectItem
v-for="row in VRChatCameraResolutions"
:key="row.name"
:value="getVRChatResolutionKey(row)">
{{ row.name }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
<br />
<div style="display: inline-block; margin-top: 10px">
<span>{{ t('dialog.config_json.spout_resolution') }}</span>
<br />
<Select
:model-value="vrchatSpoutResolutionKey"
@update:modelValue="(v) => (vrchatSpoutResolutionKey = v)">
<SelectTrigger size="sm" style="margin-top: 5px">
<SelectValue :placeholder="getVRChatSpoutResolution()" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem
v-for="row in VRChatScreenshotResolutions"
:key="row.name"
:value="getVRChatResolutionKey(row)">
{{ row.name }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
<br />
<div style="display: inline-block; margin-top: 10px">
<span>{{ t('dialog.config_json.screenshot_resolution') }}</span>
<br />
<Select
:model-value="vrchatScreenshotResolutionKey"
@update:modelValue="(v) => (vrchatScreenshotResolutionKey = v)">
<SelectTrigger size="sm" style="margin-top: 5px">
<SelectValue :placeholder="getVRChatScreenshotResolution()" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem
v-for="row in VRChatScreenshotResolutions"
:key="row.name"
:value="getVRChatResolutionKey(row)">
{{ row.name }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
<br />
<label class="inline-flex items-center gap-2" style="margin-top: 5px; display: block">
<Checkbox
v-model="VRChatConfigFile.picture_output_split_by_date"
@update:modelValue="refreshDialogValues" />
<span>{{ t('dialog.config_json.picture_sort_by_date') }}</span>
</label>
<label class="inline-flex items-center gap-2" style="margin-top: 5px; display: block">
<Checkbox v-model="VRChatConfigFile.disableRichPresence" @update:modelValue="refreshDialogValues" />
<span>{{ t('dialog.config_json.disable_discord_presence') }}</span>
</label>
</div>
<template #footer>
<div style="display: flex; align-items: center; justify-content: space-between">
<div>
<Button <Button
variant="ghost" class="rounded-full"
@click="openExternalLink('https://docs.vrchat.com/docs/configuration-file')" variant="outline"
>{{ t('dialog.config_json.vrchat_docs') }}</Button size="icon-sm"
:disabled="VRChatCacheSizeLoading"
style="margin-left: 5px"
@click="getVRChatCacheSize">
<Spinner v-if="VRChatCacheSizeLoading" />
<RefreshCw v-else />
</Button>
</TooltipWrapper>
<div style="margin-top: 10px">
<span style="margin-right: 5px">{{ t('dialog.config_json.delete_all_cache') }}</span>
<Button
size="sm"
variant="outline"
style="margin-left: 5px"
@click="showDeleteAllVRChatCacheConfirm"
>{{ t('dialog.config_json.delete_cache') }}</Button
> >
</div> </div>
<div>
<Button variant="secondary" class="mr-2" @click="closeDialog">{{ <div style="margin-top: 10px">
t('dialog.config_json.cancel') <span style="margin-right: 5px">{{ t('dialog.config_json.delete_old_cache') }}</span>
}}</Button> <Button size="sm" variant="outline" style="margin-left: 5px" @click="sweepVRChatCache">{{
<Button :disabled="loading" @click="saveVRChatConfigFile">{{ t('dialog.config_json.sweep_cache')
t('dialog.config_json.save')
}}</Button> }}</Button>
</div> </div>
<div v-for="(item, value) in VRChatConfigList" :key="value" style="display: block; margin-top: 10px">
<span style="word-break: keep-all">{{ item.name }}:</span>
<div style="display: flex">
<InputGroupAction
v-model="VRChatConfigFile[value]"
:placeholder="item.default"
size="sm"
:type="item.type ? item.type : 'text'"
:min="item.min"
:max="item.max"
@input="refreshDialogValues"
style="flex: 1; margin-top: 5px">
<template #actions>
<Button
size="sm"
variant="outline"
v-if="item.folderBrowser"
@click="openConfigFolderBrowser(value)">
</Button>
</template>
</InputGroupAction>
</div>
</div>
<div style="display: inline-block; margin-top: 10px">
<span>{{ t('dialog.config_json.camera_resolution') }}</span>
<br />
<Select
:model-value="vrchatCameraResolutionKey"
@update:modelValue="(v) => (vrchatCameraResolutionKey = v)">
<SelectTrigger size="sm" style="margin-top: 5px">
<SelectValue :placeholder="getVRChatCameraResolution()" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem
v-for="row in VRChatCameraResolutions"
:key="row.name"
:value="getVRChatResolutionKey(row)">
{{ row.name }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
<br />
<div style="display: inline-block; margin-top: 10px">
<span>{{ t('dialog.config_json.spout_resolution') }}</span>
<br />
<Select
:model-value="vrchatSpoutResolutionKey"
@update:modelValue="(v) => (vrchatSpoutResolutionKey = v)">
<SelectTrigger size="sm" style="margin-top: 5px">
<SelectValue :placeholder="getVRChatSpoutResolution()" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem
v-for="row in VRChatScreenshotResolutions"
:key="row.name"
:value="getVRChatResolutionKey(row)">
{{ row.name }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
<br />
<div style="display: inline-block; margin-top: 10px">
<span>{{ t('dialog.config_json.screenshot_resolution') }}</span>
<br />
<Select
:model-value="vrchatScreenshotResolutionKey"
@update:modelValue="(v) => (vrchatScreenshotResolutionKey = v)">
<SelectTrigger size="sm" style="margin-top: 5px">
<SelectValue :placeholder="getVRChatScreenshotResolution()" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem
v-for="row in VRChatScreenshotResolutions"
:key="row.name"
:value="getVRChatResolutionKey(row)">
{{ row.name }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
<br />
<label class="inline-flex items-center gap-2" style="margin-top: 5px; display: block">
<Checkbox
v-model="VRChatConfigFile.picture_output_split_by_date"
@update:modelValue="refreshDialogValues" />
<span>{{ t('dialog.config_json.picture_sort_by_date') }}</span>
</label>
<label class="inline-flex items-center gap-2" style="margin-top: 5px; display: block">
<Checkbox v-model="VRChatConfigFile.disableRichPresence" @update:modelValue="refreshDialogValues" />
<span>{{ t('dialog.config_json.disable_discord_presence') }}</span>
</label>
</div> </div>
</template> <DialogFooter>
</el-dialog> <div style="display: flex; align-items: center; justify-content: space-between">
<div>
<Button
variant="ghost"
@click="openExternalLink('https://docs.vrchat.com/docs/configuration-file')"
>{{ t('dialog.config_json.vrchat_docs') }}</Button
>
</div>
<div>
<Button variant="secondary" class="mr-2" @click="closeDialog">{{
t('dialog.config_json.cancel')
}}</Button>
<Button :disabled="loading" @click="saveVRChatConfigFile">{{
t('dialog.config_json.save')
}}</Button>
</div>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';

View File

@@ -1,34 +1,37 @@
<template> <template>
<el-dialog <Dialog :open="isYouTubeApiDialogVisible" @update:open="(open) => (open ? null : closeDialog())">
class="x-dialog" <DialogContent>
:model-value="isYouTubeApiDialogVisible" <DialogHeader>
:title="t('dialog.youtube_api.header')" <DialogTitle>{{ t('dialog.youtube_api.header') }}</DialogTitle>
width="450px" </DialogHeader>
@close="closeDialog"> <div style="font-size: 12px">{{ t('dialog.youtube_api.description') }} <br /></div>
<div style="font-size: 12px">{{ t('dialog.youtube_api.description') }} <br /></div>
<InputGroupTextareaField <InputGroupTextareaField
v-model="youTubeApiKey" v-model="youTubeApiKey"
:placeholder="t('dialog.youtube_api.placeholder')" :placeholder="t('dialog.youtube_api.placeholder')"
:maxlength="39" :maxlength="39"
:rows="2" :rows="2"
class="mt-2.5" class="mt-2.5"
show-count /> show-count />
<template #footer> <DialogFooter>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<Button variant="outline" @click="openExternalLink('https://smashballoon.com/doc/youtube-api-key/')"> <Button
{{ t('dialog.youtube_api.guide') }} variant="outline"
</Button> @click="openExternalLink('https://smashballoon.com/doc/youtube-api-key/')">
<Button style="margin-left: auto" @click="testYouTubeApiKey"> {{ t('dialog.youtube_api.guide') }}
{{ t('dialog.youtube_api.save') }} </Button>
</Button> <Button style="margin-left: auto" @click="testYouTubeApiKey">
</div> {{ t('dialog.youtube_api.save') }}
</template> </Button>
</el-dialog> </div>
</DialogFooter>
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';

View File

@@ -1,30 +1,31 @@
<template> <template>
<el-dialog <Dialog :open="isEditInviteMessageDialogVisible" @update:open="(open) => !open && closeDialog()">
class="x-dialog" <DialogContent class="sm:max-w-sm">
:model-value="isEditInviteMessageDialogVisible" <DialogHeader>
:title="t('dialog.edit_invite_message.header')" <DialogTitle>{{ t('dialog.edit_invite_message.header') }}</DialogTitle>
width="400px" </DialogHeader>
@close="closeDialog"> <div style="font-size: 12px">
<div style="font-size: 12px"> <span>{{ t('dialog.edit_invite_message.description') }}</span>
<span>{{ t('dialog.edit_invite_message.description') }}</span> <InputGroupTextareaField
<InputGroupTextareaField v-model="message"
v-model="message" :maxlength="64"
:maxlength="64" :rows="2"
:rows="2" class="mt-2.5"
class="mt-2.5" placeholder=""
placeholder="" show-count />
show-count /> </div>
</div> <DialogFooter>
<template #footer> <Button variant="secondary" class="mr-2" @click="closeDialog">{{
<Button variant="secondary" class="mr-2" @click="closeDialog">{{ t('dialog.edit_invite_message.cancel')
t('dialog.edit_invite_message.cancel') }}</Button>
}}</Button> <Button @click="saveEditInviteMessage">{{ t('dialog.edit_invite_message.save') }}</Button>
<Button @click="saveEditInviteMessage">{{ t('dialog.edit_invite_message.save') }}</Button> </DialogFooter>
</template> </DialogContent>
</el-dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';

View File

@@ -1,45 +1,45 @@
<template> <template>
<el-dialog <Dialog :open="isEditInviteMessagesDialogVisible" @update:open="(open) => !open && closeDialog()">
class="x-dialog" <DialogContent class="sm:max-w-5xl">
:model-value="isEditInviteMessagesDialogVisible" <DialogHeader>
:title="t('dialog.edit_invite_messages.header')" <DialogTitle>{{ t('dialog.edit_invite_messages.header') }}</DialogTitle>
width="1000px" </DialogHeader>
@close="closeDialog"> <TabsUnderline v-model="activeTab" :items="editInviteTabs" :unmount-on-hide="false" class="mt-2.5">
<TabsUnderline v-model="activeTab" :items="editInviteTabs" :unmount-on-hide="false" class="mt-2.5"> <template #message>
<template #message> <DataTableLayout
<DataTableLayout style="margin-top: 10px; cursor: pointer"
style="margin-top: 10px; cursor: pointer" :table="inviteMessageTanstackTable"
:table="inviteMessageTanstackTable" :loading="false"
:loading="false" :show-pagination="false"
:show-pagination="false" :on-row-click="handleEditInviteMessageRowClick" />
:on-row-click="handleEditInviteMessageRowClick" /> </template>
</template> <template #request>
<template #request> <DataTableLayout
<DataTableLayout style="margin-top: 10px; cursor: pointer"
style="margin-top: 10px; cursor: pointer" :table="inviteRequestTanstackTable"
:table="inviteRequestTanstackTable" :loading="false"
:loading="false" :show-pagination="false"
:show-pagination="false" :on-row-click="handleEditInviteMessageRowClick" />
:on-row-click="handleEditInviteMessageRowClick" /> </template>
</template> <template #requestResponse>
<template #requestResponse> <DataTableLayout
<DataTableLayout style="margin-top: 10px; cursor: pointer"
style="margin-top: 10px; cursor: pointer" :table="inviteRequestResponseTanstackTable"
:table="inviteRequestResponseTanstackTable" :loading="false"
:loading="false" :show-pagination="false"
:show-pagination="false" :on-row-click="handleEditInviteMessageRowClick" />
:on-row-click="handleEditInviteMessageRowClick" /> </template>
</template> <template #response>
<template #response> <DataTableLayout
<DataTableLayout style="margin-top: 10px; cursor: pointer"
style="margin-top: 10px; cursor: pointer" :table="inviteResponseTanstackTable"
:table="inviteResponseTanstackTable" :loading="false"
:loading="false" :show-pagination="false"
:show-pagination="false" :on-row-click="handleEditInviteMessageRowClick" />
:on-row-click="handleEditInviteMessageRowClick" /> </template>
</template> </TabsUnderline>
</TabsUnderline> </DialogContent>
</el-dialog> </Dialog>
<template v-if="isEditInviteMessagesDialogVisible"> <template v-if="isEditInviteMessagesDialogVisible">
<EditInviteMessageDialog <EditInviteMessageDialog
v-model:isEditInviteMessageDialogVisible="isEditInviteMessageDialogVisible" v-model:isEditInviteMessageDialogVisible="isEditInviteMessageDialogVisible"
@@ -50,6 +50,7 @@
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { DataTableLayout } from '@/components/ui/data-table'; import { DataTableLayout } from '@/components/ui/data-table';
import { TabsUnderline } from '@/components/ui/tabs'; import { TabsUnderline } from '@/components/ui/tabs';

View File

@@ -1,20 +1,26 @@
<template> <template>
<el-dialog v-model="isVisible" :title="t('dialog.export_own_avatars.header')" width="650px"> <Dialog v-model:open="isVisible">
<InputGroupTextareaField <DialogContent>
v-model="exportAvatarsListCsv" <DialogHeader>
v-loading="loading" <DialogTitle>{{ t('dialog.export_own_avatars.header') }}</DialogTitle>
:rows="15" </DialogHeader>
readonly <InputGroupTextareaField
style="margin-top: 15px" v-model="exportAvatarsListCsv"
input-class="resize-none" v-loading="loading"
@click="$event.target.tagName === 'TEXTAREA' && $event.target.select()" /> :rows="15"
</el-dialog> readonly
style="margin-top: 15px"
input-class="resize-none"
@click="$event.target.tagName === 'TEXTAREA' && $event.target.select()" />
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useAvatarStore, useUserStore } from '../../../stores'; import { useAvatarStore, useUserStore } from '../../../stores';

View File

@@ -1,26 +1,27 @@
<template> <template>
<el-dialog <Dialog :open="discordNamesDialogVisible" @update:open="(open) => !open && closeDialog()">
class="x-dialog" <DialogContent>
:model-value="discordNamesDialogVisible" <DialogHeader>
:title="t('dialog.discord_names.header')" <DialogTitle>{{ t('dialog.discord_names.header') }}</DialogTitle>
width="650px" </DialogHeader>
@close="closeDialog"> <div style="font-size: 12px">
<div style="font-size: 12px"> {{ t('dialog.discord_names.description') }}
{{ t('dialog.discord_names.description') }} </div>
</div> <InputGroupTextareaField
<InputGroupTextareaField v-model="discordNamesContent"
v-model="discordNamesContent" :rows="15"
:rows="15" readonly
readonly style="margin-top: 15px"
style="margin-top: 15px" input-class="resize-none" />
input-class="resize-none" /> </DialogContent>
</el-dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useUserStore } from '../../../stores'; import { useUserStore } from '../../../stores';

View File

@@ -1,29 +1,35 @@
<template> <template>
<el-dialog :title="t('dialog.export_friends_list.header')" v-model="isVisible" width="650px"> <Dialog v-model:open="isVisible">
<TabsUnderline default-value="csv" :items="exportFriendsTabs" :unmount-on-hide="false" class="mt-2.5"> <DialogContent>
<template #csv> <DialogHeader>
<InputGroupTextareaField <DialogTitle>{{ t('dialog.export_friends_list.header') }}</DialogTitle>
v-model="exportFriendsListCsv" </DialogHeader>
:rows="15" <TabsUnderline default-value="csv" :items="exportFriendsTabs" :unmount-on-hide="false" class="mt-2.5">
readonly <template #csv>
style="margin-top: 15px" <InputGroupTextareaField
input-class="resize-none" v-model="exportFriendsListCsv"
@click="$event.target.tagName === 'TEXTAREA' && $event.target.select()" /> :rows="15"
</template> readonly
<template #json> style="margin-top: 15px"
<InputGroupTextareaField input-class="resize-none"
v-model="exportFriendsListJson" @click="$event.target.tagName === 'TEXTAREA' && $event.target.select()" />
:rows="15" </template>
readonly <template #json>
style="margin-top: 15px" <InputGroupTextareaField
input-class="resize-none" v-model="exportFriendsListJson"
@click="$event.target.tagName === 'TEXTAREA' && $event.target.select()" /> :rows="15"
</template> readonly
</TabsUnderline> style="margin-top: 15px"
</el-dialog> input-class="resize-none"
@click="$event.target.tagName === 'TEXTAREA' && $event.target.select()" />
</template>
</TabsUnderline>
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { InputGroupTextareaField } from '@/components/ui/input-group'; import { InputGroupTextareaField } from '@/components/ui/input-group';
import { TabsUnderline } from '@/components/ui/tabs'; import { TabsUnderline } from '@/components/ui/tabs';

View File

@@ -1,119 +1,124 @@
<template> <template>
<el-dialog <Dialog :open="visible" @update:open="(open) => (open ? null : closeDialog())">
class="x-dialog" <DialogContent class="x-dialog w-[90vw] max-w-[90vw] h-[80vh] overflow-hidden">
:model-value="visible" <DialogHeader>
:title="t('dialog.group_calendar.header')" <div class="dialog-title-container">
width="90vw" <DialogTitle>{{ t('dialog.group_calendar.header') }}</DialogTitle>
height="80vh" <Button size="sm" variant="outline" @click="toggleViewMode" class="view-toggle-btn">
@close="closeDialog"> {{
<template #header> viewMode === 'timeline'
<div class="dialog-title-container"> ? t('dialog.group_calendar.list_view')
<span>{{ t('dialog.group_calendar.header') }}</span> : t('dialog.group_calendar.calendar_view')
<Button size="sm" variant="outline" @click="toggleViewMode" class="view-toggle-btn"> }}
{{ </Button>
viewMode === 'timeline' </div>
? t('dialog.group_calendar.list_view') <div class="featured-switch">
: t('dialog.group_calendar.calendar_view') <span class="featured-switch-text">{{ t('dialog.group_calendar.featured_events') }}</span>
}} <Switch v-model="showFeaturedEvents" @update:modelValue="toggleFeaturedEvents" />
</Button> </div>
</div> </DialogHeader>
<div class="featured-switch"> <div class="top-content">
<span class="featured-switch-text">{{ t('dialog.group_calendar.featured_events') }}</span> <transition name="el-fade-in-linear" mode="out-in">
<Switch v-model="showFeaturedEvents" @update:modelValue="toggleFeaturedEvents" /> <div v-if="viewMode === 'timeline'" key="timeline" class="timeline-view">
</div> <div class="timeline-container">
</template> <el-timeline v-if="groupedTimelineEvents.length">
<div class="top-content"> <el-timeline-item
<transition name="el-fade-in-linear" mode="out-in"> v-for="(timeGroup, key) of groupedTimelineEvents"
<div v-if="viewMode === 'timeline'" key="timeline" class="timeline-view"> :key="key"
<div class="timeline-container"> :timestamp="
<el-timeline v-if="groupedTimelineEvents.length"> dayjs(timeGroup.startsAt).format('MM-DD ddd') + ' ' + timeGroup.startTime
<el-timeline-item "
v-for="(timeGroup, key) of groupedTimelineEvents" placement="top">
:key="key" <div class="time-group-container">
:timestamp="dayjs(timeGroup.startsAt).format('MM-DD ddd') + ' ' + timeGroup.startTime" <GroupCalendarEventCard
placement="top"> v-for="value in timeGroup.events"
<div class="time-group-container"> :key="value.id"
<GroupCalendarEventCard :event="value"
v-for="value in timeGroup.events" mode="timeline"
:key="value.id" :is-following="isEventFollowing(value.id)"
:event="value" :card-class="{ 'grouped-card': timeGroup.events.length > 1 }"
mode="timeline" @update-following-calendar-data="updateFollowingCalendarData"
:is-following="isEventFollowing(value.id)" @click-action="showGroupDialog(value.ownerId)" />
:card-class="{ 'grouped-card': timeGroup.events.length > 1 }" </div>
@update-following-calendar-data="updateFollowingCalendarData" </el-timeline-item>
@click-action="showGroupDialog(value.ownerId)" /> </el-timeline>
</div> <div v-else>{{ t('dialog.group_calendar.no_events') }}</div>
</el-timeline-item> </div>
</el-timeline>
<div v-else>{{ t('dialog.group_calendar.no_events') }}</div>
</div>
<div class="calendar-container"> <div class="calendar-container">
<el-calendar v-model="selectedDay" v-loading="isLoading"> <el-calendar v-model="selectedDay" v-loading="isLoading">
<template #date-cell="{ data }"> <template #date-cell="{ data }">
<div class="date"> <div class="date">
<div
class="calendar-date-content"
:class="{ 'has-events': filteredCalendar[formatDateKey(data.date)]?.length }">
{{ dayjs(data.date).format('D') }}
<div <div
v-if="filteredCalendar[formatDateKey(data.date)]?.length" class="calendar-date-content"
class="calendar-event-badge" :class="{
:class="followingCalendarDate[formatDateKey(data.date)] ? 'has-following' : 'no-following'"> 'has-events': filteredCalendar[formatDateKey(data.date)]?.length
{{ filteredCalendar[formatDateKey(data.date)]?.length }} }">
{{ dayjs(data.date).format('D') }}
<div
v-if="filteredCalendar[formatDateKey(data.date)]?.length"
class="calendar-event-badge"
:class="
followingCalendarDate[formatDateKey(data.date)]
? 'has-following'
: 'no-following'
">
{{ filteredCalendar[formatDateKey(data.date)]?.length }}
</div>
</div> </div>
</div> </div>
</div> </template>
</template> </el-calendar>
</el-calendar> </div>
</div>
</div>
<div v-else key="grid" class="grid-view">
<div class="search-container">
<InputGroupSearch
v-model="searchQuery"
size="sm"
:placeholder="t('dialog.group_calendar.search_placeholder')"
class="search-input" />
</div> </div>
<div v-else key="grid" class="grid-view">
<div class="search-container">
<InputGroupSearch
v-model="searchQuery"
size="sm"
:placeholder="t('dialog.group_calendar.search_placeholder')"
class="search-input" />
</div>
<div class="groups-grid" v-loading="isLoading"> <div class="groups-grid" v-loading="isLoading">
<div v-if="filteredGroupEvents.length" class="groups-container"> <div v-if="filteredGroupEvents.length" class="groups-container">
<div v-for="group in filteredGroupEvents" :key="group.groupId" class="group-row"> <div v-for="group in filteredGroupEvents" :key="group.groupId" class="group-row">
<div class="group-header" @click="toggleGroup(group.groupId)"> <div class="group-header" @click="toggleGroup(group.groupId)">
<ArrowRight <ArrowRight
class="rotation-transition" class="rotation-transition"
:class="{ rotate: !groupCollapsed[group.groupId] }" /> :class="{ rotate: !groupCollapsed[group.groupId] }" />
{{ group.groupName }} {{ group.groupName }}
</div> </div>
<div class="events-row" v-show="!groupCollapsed[group.groupId]"> <div class="events-row" v-show="!groupCollapsed[group.groupId]">
<GroupCalendarEventCard <GroupCalendarEventCard
v-for="event in group.events" v-for="event in group.events"
:key="event.id" :key="event.id"
:event="event" :event="event"
mode="grid" mode="grid"
:is-following="isEventFollowing(event.id)" :is-following="isEventFollowing(event.id)"
@update-following-calendar-data="updateFollowingCalendarData" @update-following-calendar-data="updateFollowingCalendarData"
@click-action="showGroupDialog(event.ownerId)" @click-action="showGroupDialog(event.ownerId)"
card-class="grid-card" /> card-class="grid-card" />
</div>
</div> </div>
</div> </div>
</div> <div v-else class="no-events">
<div v-else class="no-events"> {{
{{ searchQuery
searchQuery ? t('dialog.group_calendar.search_no_matching')
? t('dialog.group_calendar.search_no_matching') : t('dialog.group_calendar.search_no_this_month')
: t('dialog.group_calendar.search_no_this_month') }}
}} </div>
</div> </div>
</div> </div>
</div> </transition>
</transition> </div>
</div> </DialogContent>
</el-dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, onMounted, ref, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import { ArrowRight } from 'lucide-vue-next'; import { ArrowRight } from 'lucide-vue-next';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';

View File

@@ -1,68 +1,69 @@
<template> <template>
<el-dialog <Dialog :open="isNoteExportDialogVisible" @update:open="(open) => !open && closeDialog()">
class="x-dialog" <DialogContent class="sm:max-w-5xl">
:model-value="isNoteExportDialogVisible" <DialogHeader>
:title="t('dialog.note_export.header')" <DialogTitle>{{ t('dialog.note_export.header') }}</DialogTitle>
width="1000px" </DialogHeader>
@close="closeDialog"> <div style="font-size: 12px">
<div style="font-size: 12px"> {{ t('dialog.note_export.description1') }} <br />
{{ t('dialog.note_export.description1') }} <br /> {{ t('dialog.note_export.description2') }} <br />
{{ t('dialog.note_export.description2') }} <br /> {{ t('dialog.note_export.description3') }} <br />
{{ t('dialog.note_export.description3') }} <br /> {{ t('dialog.note_export.description4') }} <br />
{{ t('dialog.note_export.description4') }} <br /> {{ t('dialog.note_export.description5') }} <br />
{{ t('dialog.note_export.description5') }} <br /> {{ t('dialog.note_export.description6') }} <br />
{{ t('dialog.note_export.description6') }} <br /> {{ t('dialog.note_export.description7') }} <br />
{{ t('dialog.note_export.description7') }} <br /> {{ t('dialog.note_export.description8') }} <br />
{{ t('dialog.note_export.description8') }} <br /> </div>
</div>
<Button <Button
size="sm" size="sm"
class="mr-2" class="mr-2"
variant="outline" variant="outline"
:disabled="loading" :disabled="loading"
style="margin-top: 10px" style="margin-top: 10px"
@click="updateNoteExportDialog"> @click="updateNoteExportDialog">
{{ t('dialog.note_export.refresh') }} {{ t('dialog.note_export.refresh') }}
</Button>
<Button
size="sm"
class="mr-2"
variant="outline"
:disabled="loading"
style="margin-top: 10px"
@click="exportNoteExport">
{{ t('dialog.note_export.export') }}
</Button>
<Button v-if="loading" size="sm" variant="outline" style="margin-top: 10px" @click="cancelNoteExport">
{{ t('dialog.note_export.cancel') }}
</Button>
<span v-if="loading" style="margin: 10px">
<Loader2 style="margin-right: 5px" />
{{ t('dialog.note_export.progress') }} {{ progress }}/{{ progressTotal }}
</span>
<template v-if="errors">
<Button size="sm" variant="outline" @click="errors = ''">
{{ t('dialog.note_export.clear_errors') }}
</Button> </Button>
<h2 style="font-weight: bold; margin: 0"> <Button
{{ t('dialog.note_export.errors') }} size="sm"
</h2> class="mr-2"
<pre style="white-space: pre-wrap; font-size: 12px" v-text="errors"></pre> variant="outline"
</template> :disabled="loading"
style="margin-top: 10px"
@click="exportNoteExport">
{{ t('dialog.note_export.export') }}
</Button>
<Button v-if="loading" size="sm" variant="outline" style="margin-top: 10px" @click="cancelNoteExport">
{{ t('dialog.note_export.cancel') }}
</Button>
<span v-if="loading" style="margin: 10px">
<Loader2 style="margin-right: 5px" />
{{ t('dialog.note_export.progress') }} {{ progress }}/{{ progressTotal }}
</span>
<DataTableLayout <template v-if="errors">
class="min-w-0 w-full" <Button size="sm" variant="outline" @click="errors = ''">
:table="table" {{ t('dialog.note_export.clear_errors') }}
:loading="loading" </Button>
:table-style="tableStyle" <h2 style="font-weight: bold; margin: 0">
:show-pagination="false" {{ t('dialog.note_export.errors') }}
style="margin-top: 10px" /> </h2>
</el-dialog> <pre style="white-space: pre-wrap; font-size: 12px" v-text="errors"></pre>
</template>
<DataTableLayout
class="min-w-0 w-full"
:table="table"
:loading="loading"
:table-style="tableStyle"
:show-pagination="false"
style="margin-top: 10px" />
</DialogContent>
</Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { DataTableLayout } from '@/components/ui/data-table'; import { DataTableLayout } from '@/components/ui/data-table';