mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-14 04:13:52 +02:00
replace el-dialog
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,15 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="changeAvatarImageDialogVisible"
|
||||
:title="t('dialog.change_content_image.avatar')"
|
||||
width="850px"
|
||||
append-to-body
|
||||
@close="closeDialog">
|
||||
<div>
|
||||
<Dialog
|
||||
:open="changeAvatarImageDialogVisible"
|
||||
@update:open="(open) => {
|
||||
if (!open) closeDialog();
|
||||
}">
|
||||
<DialogContent class="x-dialog sm:max-w-212.5">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.change_content_image.avatar') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div>
|
||||
<input
|
||||
id="AvatarImageUploadButton"
|
||||
type="file"
|
||||
@@ -27,12 +30,14 @@
|
||||
<div class="x-change-image-item">
|
||||
<img :src="previousImageUrl" class="img-size" loading="lazy" />
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Upload } from 'lucide-vue-next';
|
||||
import { ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
ref="setAvatarStylesDialog"
|
||||
class="x-dialog"
|
||||
:model-value="setAvatarStylesDialog.visible"
|
||||
:title="t('dialog.set_avatar_styles.header')"
|
||||
width="400px"
|
||||
append-to-body
|
||||
@close="closeSetAvatarStylesDialog">
|
||||
<template v-if="setAvatarStylesDialog.visible">
|
||||
<div>
|
||||
<Dialog
|
||||
:open="setAvatarStylesDialog.visible"
|
||||
@update:open="(open) => {
|
||||
if (!open) closeSetAvatarStylesDialog();
|
||||
}">
|
||||
<DialogContent class="x-dialog sm:max-w-100">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.set_avatar_styles.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<template v-if="setAvatarStylesDialog.visible">
|
||||
<div>
|
||||
<span>{{ t('dialog.set_avatar_styles.primary_style') }}</span>
|
||||
<br />
|
||||
<Select
|
||||
@@ -64,21 +66,23 @@
|
||||
style="margin-top: 10px"
|
||||
input-class="resize-none"
|
||||
@update:modelValue="(v) => updateDialog({ authorTags: v })" />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<Button variant="secondary" class="mr-2" @click="closeSetAvatarStylesDialog">{{
|
||||
t('dialog.set_avatar_styles.cancel')
|
||||
}}</Button>
|
||||
<Button @click="saveSetAvatarStylesDialog">
|
||||
{{ t('dialog.set_avatar_styles.save') }}
|
||||
</Button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" class="mr-2" @click="closeSetAvatarStylesDialog">{{
|
||||
t('dialog.set_avatar_styles.cancel')
|
||||
}}</Button>
|
||||
<Button @click="saveSetAvatarStylesDialog">
|
||||
{{ t('dialog.set_avatar_styles.save') }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -1,102 +1,115 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
ref="setAvatarTagsDialog"
|
||||
class="x-dialog"
|
||||
:model-value="setAvatarTagsDialog.visible"
|
||||
@close="closeSetAvatarTagsDialog"
|
||||
:title="t('dialog.set_avatar_tags.header')"
|
||||
width="780px"
|
||||
append-to-body>
|
||||
<template v-if="setAvatarTagsDialog.visible">
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setAvatarTagsDialog.contentHorror" @update:modelValue="updateSelectedAvatarTags" />
|
||||
<span>{{ t('dialog.set_avatar_tags.content_horror') }}</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setAvatarTagsDialog.contentGore" @update:modelValue="updateSelectedAvatarTags" />
|
||||
<span>{{ t('dialog.set_avatar_tags.content_gore') }}</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setAvatarTagsDialog.contentViolence" @update:modelValue="updateSelectedAvatarTags" />
|
||||
<span>{{ t('dialog.set_avatar_tags.content_violence') }}</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setAvatarTagsDialog.contentAdult" @update:modelValue="updateSelectedAvatarTags" />
|
||||
<span>{{ t('dialog.set_avatar_tags.content_adult') }}</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setAvatarTagsDialog.contentSex" @update:modelValue="updateSelectedAvatarTags" />
|
||||
<span>{{ t('dialog.set_avatar_tags.content_sex') }}</span>
|
||||
</label>
|
||||
<br />
|
||||
<InputGroupTextareaField
|
||||
v-model="setAvatarTagsDialog.selectedTagsCsv"
|
||||
:rows="2"
|
||||
:placeholder="t('dialog.set_avatar_tags.custom_tags_placeholder')"
|
||||
style="margin-top: 10px"
|
||||
input-class="resize-none"
|
||||
@input="updateInputAvatarTags" />
|
||||
<br />
|
||||
<br />
|
||||
<template
|
||||
v-if="setAvatarTagsDialog.ownAvatars.length === props.setAvatarTagsDialog.selectedAvatarIds.length">
|
||||
<Button size="sm" variant="outline" @click="setAvatarTagsSelectToggle">{{
|
||||
t('dialog.set_avatar_tags.select_none')
|
||||
}}</Button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Button size="sm" variant="outline" @click="setAvatarTagsSelectToggle">{{
|
||||
t('dialog.set_avatar_tags.select_all')
|
||||
}}</Button>
|
||||
</template>
|
||||
<span style="margin-left: 5px"
|
||||
>{{ props.setAvatarTagsDialog.selectedAvatarIds.length }} /
|
||||
{{ setAvatarTagsDialog.ownAvatars.length }}</span
|
||||
>
|
||||
<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" />
|
||||
<Dialog
|
||||
:open="setAvatarTagsDialog.visible"
|
||||
@update:open="
|
||||
(open) => {
|
||||
if (!open) closeSetAvatarTagsDialog();
|
||||
}
|
||||
">
|
||||
<DialogContent class="x-dialog sm:max-w-195">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.set_avatar_tags.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<template v-if="setAvatarTagsDialog.visible">
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox
|
||||
v-model="setAvatarTagsDialog.contentHorror"
|
||||
@update:modelValue="updateSelectedAvatarTags" />
|
||||
<span>{{ t('dialog.set_avatar_tags.content_horror') }}</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setAvatarTagsDialog.contentGore" @update:modelValue="updateSelectedAvatarTags" />
|
||||
<span>{{ t('dialog.set_avatar_tags.content_gore') }}</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox
|
||||
v-model="setAvatarTagsDialog.contentViolence"
|
||||
@update:modelValue="updateSelectedAvatarTags" />
|
||||
<span>{{ t('dialog.set_avatar_tags.content_violence') }}</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox
|
||||
v-model="setAvatarTagsDialog.contentAdult"
|
||||
@update:modelValue="updateSelectedAvatarTags" />
|
||||
<span>{{ t('dialog.set_avatar_tags.content_adult') }}</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setAvatarTagsDialog.contentSex" @update:modelValue="updateSelectedAvatarTags" />
|
||||
<span>{{ t('dialog.set_avatar_tags.content_sex') }}</span>
|
||||
</label>
|
||||
<br />
|
||||
<InputGroupTextareaField
|
||||
v-model="setAvatarTagsDialog.selectedTagsCsv"
|
||||
:rows="2"
|
||||
:placeholder="t('dialog.set_avatar_tags.custom_tags_placeholder')"
|
||||
style="margin-top: 10px"
|
||||
input-class="resize-none"
|
||||
@input="updateInputAvatarTags" />
|
||||
<br />
|
||||
<br />
|
||||
<template
|
||||
v-if="setAvatarTagsDialog.ownAvatars.length === props.setAvatarTagsDialog.selectedAvatarIds.length">
|
||||
<Button size="sm" variant="outline" @click="setAvatarTagsSelectToggle">{{
|
||||
t('dialog.set_avatar_tags.select_none')
|
||||
}}</Button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Button size="sm" variant="outline" @click="setAvatarTagsSelectToggle">{{
|
||||
t('dialog.set_avatar_tags.select_all')
|
||||
}}</Button>
|
||||
</template>
|
||||
<span style="margin-left: 5px"
|
||||
>{{ props.setAvatarTagsDialog.selectedAvatarIds.length }} /
|
||||
{{ setAvatarTagsDialog.ownAvatars.length }}</span
|
||||
>
|
||||
<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 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>
|
||||
</template>
|
||||
<template #footer>
|
||||
<Button variant="secondary" class="mr-2" @click="closeSetAvatarTagsDialog">{{
|
||||
t('dialog.set_avatar_tags.cancel')
|
||||
}}</Button>
|
||||
<Button @click="saveSetAvatarTagsDialog">{{ t('dialog.set_avatar_tags.save') }}</Button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" class="mr-2" @click="closeSetAvatarTagsDialog">{{
|
||||
t('dialog.set_avatar_tags.cancel')
|
||||
}}</Button>
|
||||
<Button @click="saveSetAvatarTagsDialog">{{ t('dialog.set_avatar_tags.save') }}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<el-dialog :z-index="favoriteDialogIndex" v-model="isVisible" :title="t('dialog.favorite.header')" width="300px">
|
||||
<div v-loading="loading">
|
||||
<Dialog v-model:open="isVisible">
|
||||
<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>
|
||||
<template v-if="favoriteDialog.currentGroup && favoriteDialog.currentGroup.key">
|
||||
<Button
|
||||
@@ -23,8 +27,8 @@
|
||||
{{ group.displayName }} ({{ group.count }} / {{ group.capacity }})
|
||||
</Button>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="favoriteDialog.type === 'world'" style="margin-top: 20px">
|
||||
</div>
|
||||
<div v-if="favoriteDialog.type === 'world'" style="margin-top: 20px">
|
||||
<span style="display: block; text-align: center">{{ t('dialog.favorite.local_favorites') }}</span>
|
||||
<template v-for="group in localWorldFavoriteGroups" :key="group">
|
||||
<Button
|
||||
@@ -44,8 +48,8 @@
|
||||
{{ group }} ({{ localWorldFavGroupLength(group) }})
|
||||
</Button>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="favoriteDialog.type === 'avatar'" style="margin-top: 20px">
|
||||
</div>
|
||||
<div v-if="favoriteDialog.type === 'avatar'" style="margin-top: 20px">
|
||||
<span style="text-align: center">{{ t('dialog.favorite.local_avatar_favorites') }}</span>
|
||||
<template v-for="group in localAvatarFavoriteGroups" :key="group">
|
||||
<Button
|
||||
@@ -66,14 +70,16 @@
|
||||
{{ group }} ({{ localAvatarFavGroupLength(group) }})
|
||||
</Button>
|
||||
</template>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Check } from 'lucide-vue-next';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -81,7 +87,6 @@
|
||||
|
||||
import { useFavoriteStore, useUserStore } from '../../stores';
|
||||
import { favoriteRequest } from '../../api';
|
||||
import { getNextDialogIndex } from '../../shared/utils/base/ui';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -107,7 +112,6 @@
|
||||
} = favoriteStore;
|
||||
const { isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
|
||||
|
||||
const favoriteDialogIndex = ref(2000);
|
||||
const groups = ref([]);
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -123,9 +127,6 @@
|
||||
(value) => {
|
||||
if (value) {
|
||||
initFavoriteDialog();
|
||||
nextTick(() => {
|
||||
favoriteDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="visible"
|
||||
class="custom-nav-dialog"
|
||||
:title="t('nav_menu.custom_nav.dialog_title')"
|
||||
width="600px"
|
||||
@close="handleClose"
|
||||
destroy-on-close>
|
||||
<Dialog :open="visible" @update:open="(open) => (open ? null : handleClose())">
|
||||
<DialogContent class="custom-nav-dialog">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('nav_menu.custom_nav.dialog_title') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="custom-nav-dialog__list" v-if="localLayout.length">
|
||||
<div
|
||||
v-for="(entry, index) in localLayout"
|
||||
@@ -90,7 +88,7 @@
|
||||
type="warning"
|
||||
:closable="false"
|
||||
:title="t('nav_menu.custom_nav.invalid_folder')" />
|
||||
<template #footer>
|
||||
<DialogFooter>
|
||||
<div class="custom-nav-dialog__footer">
|
||||
<div class="custom-nav-dialog__footer-left">
|
||||
<Button variant="outline" @click="openFolderEditor()">
|
||||
@@ -109,15 +107,21 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="folderEditor.visible"
|
||||
class="folder-editor-dialog"
|
||||
:title="folderEditor.isEditing ? t('nav_menu.custom_nav.edit_folder') : t('nav_menu.custom_nav.add_folder')"
|
||||
width="900px"
|
||||
destroy-on-close>
|
||||
<Dialog v-model:open="folderEditor.visible">
|
||||
<DialogContent class="folder-editor-dialog">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{{
|
||||
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__form">
|
||||
<InputGroupField
|
||||
@@ -189,7 +193,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<DialogFooter>
|
||||
<div class="folder-editor__footer">
|
||||
<Button
|
||||
variant="destructive"
|
||||
@@ -206,13 +210,15 @@
|
||||
{{ t('nav_menu.custom_nav.save') }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@@ -1,61 +1,66 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
v-model="gallerySelectDialog.visible"
|
||||
:title="t('dialog.gallery_select.header')"
|
||||
width="100%"
|
||||
append-to-body>
|
||||
<div>
|
||||
<span>{{ t('dialog.gallery_select.gallery') }}</span>
|
||||
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{ galleryTable.length }}/64</span>
|
||||
<br />
|
||||
<input
|
||||
id="GalleryUploadButton"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
style="display: none"
|
||||
@change="onFileChangeGallery" />
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="sm" @click="selectImageGallerySelect('', '')">
|
||||
<X />
|
||||
{{ t('dialog.gallery_select.none') }}
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" @click="refreshGalleryTable">
|
||||
<RefreshCw />
|
||||
{{ t('dialog.gallery_select.refresh') }}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="!isLocalUserVrcPlusSupporter"
|
||||
@click="displayGalleryUpload">
|
||||
<Upload />
|
||||
{{ t('dialog.gallery_select.upload') }}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<br />
|
||||
<div
|
||||
v-for="image in galleryTable"
|
||||
:key="image.id"
|
||||
class="x-friend-item"
|
||||
style="display: inline-block; margin-top: 10px; width: unset; cursor: default">
|
||||
<template v-if="image.versions && image.versions.length > 0">
|
||||
<div
|
||||
v-if="image.versions[image.versions.length - 1].file.url"
|
||||
class="vrcplus-icon"
|
||||
@click="selectImageGallerySelect(image.versions[image.versions.length - 1].file.url, image.id)">
|
||||
<img
|
||||
:src="image.versions[image.versions.length - 1].file.url"
|
||||
class="avatar"
|
||||
loading="lazy" /></div
|
||||
></template>
|
||||
<Dialog v-model:open="gallerySelectDialog.visible">
|
||||
<DialogContent class="x-dialog w-full sm:max-w-none">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.gallery_select.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div>
|
||||
<span>{{ t('dialog.gallery_select.gallery') }}</span>
|
||||
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{ galleryTable.length }}/64</span>
|
||||
<br />
|
||||
<input
|
||||
id="GalleryUploadButton"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
style="display: none"
|
||||
@change="onFileChangeGallery" />
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="sm" @click="selectImageGallerySelect('', '')">
|
||||
<X />
|
||||
{{ t('dialog.gallery_select.none') }}
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" @click="refreshGalleryTable">
|
||||
<RefreshCw />
|
||||
{{ t('dialog.gallery_select.refresh') }}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="!isLocalUserVrcPlusSupporter"
|
||||
@click="displayGalleryUpload">
|
||||
<Upload />
|
||||
{{ t('dialog.gallery_select.upload') }}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<br />
|
||||
<div
|
||||
v-for="image in galleryTable"
|
||||
:key="image.id"
|
||||
class="x-friend-item"
|
||||
style="display: inline-block; margin-top: 10px; width: unset; cursor: default">
|
||||
<template v-if="image.versions && image.versions.length > 0">
|
||||
<div
|
||||
v-if="image.versions[image.versions.length - 1].file.url"
|
||||
class="vrcplus-icon"
|
||||
@click="
|
||||
selectImageGallerySelect(image.versions[image.versions.length - 1].file.url, image.id)
|
||||
">
|
||||
<img
|
||||
:src="image.versions[image.versions.length - 1].file.url"
|
||||
class="avatar"
|
||||
loading="lazy" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<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 { ButtonGroup } from '@/components/ui/button-group';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,34 +1,41 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="isGroupLogsExportDialogVisible"
|
||||
:title="t('dialog.group_member_moderation.export_logs')"
|
||||
width="650px"
|
||||
append-to-body
|
||||
@close="setIsGroupLogsExportDialogVisible">
|
||||
<div style="margin-bottom: 10px" class="flex flex-col gap-2">
|
||||
<label
|
||||
v-for="option in checkGroupsLogsExportLogsOptions"
|
||||
:key="option.label"
|
||||
class="inline-flex items-center gap-2">
|
||||
<Checkbox
|
||||
:model-value="checkedGroupLogsExportLogsOptions.includes(option.label)"
|
||||
@update:modelValue="(val) => toggleGroupLogsExportOption(option.label, val)" />
|
||||
<span>{{ t(option.text) }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<br />
|
||||
<InputGroupTextareaField
|
||||
v-model="groupLogsExportContent"
|
||||
:rows="15"
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
input-class="resize-none"
|
||||
@click="handleCopyGroupLogsExportContent" />
|
||||
</el-dialog>
|
||||
<Dialog
|
||||
:open="isGroupLogsExportDialogVisible"
|
||||
@update:open="
|
||||
(open) => {
|
||||
if (!open) setIsGroupLogsExportDialogVisible();
|
||||
}
|
||||
">
|
||||
<DialogContent class="x-dialog sm:max-w-162.5">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.group_member_moderation.export_logs') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div style="margin-bottom: 10px" class="flex flex-col gap-2">
|
||||
<label
|
||||
v-for="option in checkGroupsLogsExportLogsOptions"
|
||||
:key="option.label"
|
||||
class="inline-flex items-center gap-2">
|
||||
<Checkbox
|
||||
:model-value="checkedGroupLogsExportLogsOptions.includes(option.label)"
|
||||
@update:modelValue="(val) => toggleGroupLogsExportOption(option.label, val)" />
|
||||
<span>{{ t(option.text) }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<br />
|
||||
<InputGroupTextareaField
|
||||
v-model="groupLogsExportContent"
|
||||
:rows="15"
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
input-class="resize-none"
|
||||
@click="handleCopyGroupLogsExportContent" />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { ref, watch } from 'vue';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
|
||||
@@ -1,131 +1,144 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="groupPostEditDialog.visible"
|
||||
:title="t('dialog.group_post_edit.header')"
|
||||
width="650px"
|
||||
append-to-body>
|
||||
<div v-if="groupPostEditDialog.visible">
|
||||
<FieldGroup class="gap-4">
|
||||
<Field>
|
||||
<FieldLabel>{{ t('dialog.group_post_edit.title') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<InputGroupField v-model="groupPostEditDialog.title" size="sm" />
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel>{{ t('dialog.group_post_edit.message') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<InputGroupTextareaField
|
||||
v-model="groupPostEditDialog.text"
|
||||
:rows="4"
|
||||
style="margin-top: 10px"
|
||||
input-class="resize-none" />
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<Field v-if="!groupPostEditDialog.postId">
|
||||
<FieldLabel class="sr-only">{{ t('dialog.group_post_edit.send_notification') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="groupPostEditDialog.sendNotification" />
|
||||
<span>{{ t('dialog.group_post_edit.send_notification') }}</span>
|
||||
</label>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel>{{ t('dialog.group_post_edit.post_visibility') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<RadioGroup v-model="groupPostEditDialog.visibility" class="flex items-center gap-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="groupPostVisibility-public" value="public" />
|
||||
<label for="groupPostVisibility-public">
|
||||
{{ t('dialog.group_post_edit.visibility_public') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="groupPostVisibility-group" value="group" />
|
||||
<label for="groupPostVisibility-group">
|
||||
{{ t('dialog.group_post_edit.visibility_group') }}
|
||||
</label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<Field v-if="groupPostEditDialog.visibility === 'group'">
|
||||
<FieldLabel>{{ t('dialog.new_instance.roles') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<Select
|
||||
multiple
|
||||
:model-value="Array.isArray(groupPostEditDialog.roleIds) ? groupPostEditDialog.roleIds : []"
|
||||
@update:modelValue="handleRoleIdsChange">
|
||||
<SelectTrigger size="sm" class="w-full">
|
||||
<SelectValue>
|
||||
<span class="truncate">
|
||||
{{ selectedRoleSummary || t('dialog.new_instance.role_placeholder') }}
|
||||
</span>
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="role in groupPostEditDialog.groupRef?.roles ?? []"
|
||||
:key="role.id"
|
||||
:value="role.id">
|
||||
{{ role.name }}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel>{{ t('dialog.group_post_edit.image') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<template v-if="gallerySelectDialog.selectedFileId">
|
||||
<div style="display: inline-block; flex: none; margin-right: 5px">
|
||||
<img
|
||||
:src="gallerySelectDialog.selectedImageUrl"
|
||||
style="flex: none; width: 60px; height: 60px; border-radius: 4px; 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') }}
|
||||
<Dialog v-model:open="groupPostEditDialog.visible">
|
||||
<DialogContent class="sm:max-w-162.5">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.group_post_edit.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div v-if="groupPostEditDialog.visible">
|
||||
<FieldGroup class="gap-4">
|
||||
<Field>
|
||||
<FieldLabel>{{ t('dialog.group_post_edit.title') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<InputGroupField v-model="groupPostEditDialog.title" size="sm" />
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel>{{ t('dialog.group_post_edit.message') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<InputGroupTextareaField
|
||||
v-model="groupPostEditDialog.text"
|
||||
:rows="4"
|
||||
style="margin-top: 10px"
|
||||
input-class="resize-none" />
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<Field v-if="!groupPostEditDialog.postId">
|
||||
<FieldLabel class="sr-only">{{ t('dialog.group_post_edit.send_notification') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="groupPostEditDialog.sendNotification" />
|
||||
<span>{{ t('dialog.group_post_edit.send_notification') }}</span>
|
||||
</label>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel>{{ t('dialog.group_post_edit.post_visibility') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<RadioGroup v-model="groupPostEditDialog.visibility" class="flex items-center gap-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="groupPostVisibility-public" value="public" />
|
||||
<label for="groupPostVisibility-public">
|
||||
{{ t('dialog.group_post_edit.visibility_public') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="groupPostVisibility-group" value="group" />
|
||||
<label for="groupPostVisibility-group">
|
||||
{{ t('dialog.group_post_edit.visibility_group') }}
|
||||
</label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<Field v-if="groupPostEditDialog.visibility === 'group'">
|
||||
<FieldLabel>{{ t('dialog.new_instance.roles') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<Select
|
||||
multiple
|
||||
:model-value="
|
||||
Array.isArray(groupPostEditDialog.roleIds) ? groupPostEditDialog.roleIds : []
|
||||
"
|
||||
@update:modelValue="handleRoleIdsChange">
|
||||
<SelectTrigger size="sm" class="w-full">
|
||||
<SelectValue>
|
||||
<span class="truncate">
|
||||
{{ selectedRoleSummary || t('dialog.new_instance.role_placeholder') }}
|
||||
</span>
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="role in groupPostEditDialog.groupRef?.roles ?? []"
|
||||
:key="role.id"
|
||||
:value="role.id">
|
||||
{{ role.name }}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel>{{ t('dialog.group_post_edit.image') }}</FieldLabel>
|
||||
<FieldContent>
|
||||
<template v-if="gallerySelectDialog.selectedFileId">
|
||||
<div style="display: inline-block; flex: none; margin-right: 5px">
|
||||
<img
|
||||
:src="gallerySelectDialog.selectedImageUrl"
|
||||
style="
|
||||
flex: none;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 4px;
|
||||
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>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Button size="sm" variant="outline" @click="showGallerySelectDialog">
|
||||
{{ 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>
|
||||
</template>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</div>
|
||||
</template>
|
||||
<GallerySelectDialog
|
||||
:gallery-select-dialog="gallerySelectDialog"
|
||||
:gallery-table="galleryTable"
|
||||
@refresh-gallery-table="refreshGalleryTable" />
|
||||
</el-dialog>
|
||||
|
||||
<DialogFooter>
|
||||
<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>
|
||||
</DialogFooter>
|
||||
|
||||
<GallerySelectDialog
|
||||
:gallery-select-dialog="gallerySelectDialog"
|
||||
:gallery-table="galleryTable"
|
||||
@refresh-gallery-table="refreshGalleryTable" />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field';
|
||||
import { InputGroupField, InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
@@ -1,35 +1,42 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="editAndSendInviteDialog.visible"
|
||||
:title="t('dialog.edit_send_invite_message.header')"
|
||||
width="400px"
|
||||
append-to-body
|
||||
@close="cancelEditAndSendInvite">
|
||||
<div style="font-size: 12px">
|
||||
<span>{{ t('dialog.edit_send_invite_message.description') }}</span>
|
||||
</div>
|
||||
<Dialog
|
||||
:open="editAndSendInviteDialog.visible"
|
||||
@update:open="
|
||||
(open) => {
|
||||
if (!open) cancelEditAndSendInvite();
|
||||
}
|
||||
">
|
||||
<DialogContent class="x-dialog sm:max-w-100">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.edit_send_invite_message.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<InputGroupTextareaField
|
||||
v-model="editAndSendInviteDialog.newMessage"
|
||||
:maxlength="64"
|
||||
:rows="2"
|
||||
class="mt-2.5"
|
||||
placeholder=""
|
||||
show-count />
|
||||
<div style="font-size: 12px">
|
||||
<span>{{ t('dialog.edit_send_invite_message.description') }}</span>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<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>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<InputGroupTextareaField
|
||||
v-model="editAndSendInviteDialog.newMessage"
|
||||
:maxlength="64"
|
||||
:rows="2"
|
||||
class="mt-2.5"
|
||||
placeholder=""
|
||||
show-count />
|
||||
|
||||
<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>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
@@ -1,89 +1,97 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="inviteDialog.visible"
|
||||
@close="closeInviteDialog"
|
||||
:title="t('dialog.invite.header')"
|
||||
width="500px"
|
||||
append-to-body>
|
||||
<div v-if="inviteDialog.visible" v-loading="inviteDialog.loading">
|
||||
<Location :location="inviteDialog.worldId" :link="false" />
|
||||
<br />
|
||||
<Button size="sm" class="mr-2" variant="outline" style="margin-top: 10px" @click="addSelfToInvite">{{
|
||||
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
|
||||
>
|
||||
<Dialog
|
||||
:open="inviteDialog.visible"
|
||||
@update:open="
|
||||
(open) => {
|
||||
if (!open) closeInviteDialog();
|
||||
}
|
||||
">
|
||||
<DialogContent class="x-dialog sm:max-w-125">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.invite.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div style="width: 100%; margin-top: 15px">
|
||||
<VirtualCombobox
|
||||
:model-value="Array.isArray(inviteDialog.userIds) ? inviteDialog.userIds : []"
|
||||
@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>
|
||||
<div v-if="inviteDialog.visible" v-loading="inviteDialog.loading">
|
||||
<Location :location="inviteDialog.worldId" :link="false" />
|
||||
<br />
|
||||
<Button size="sm" class="mr-2" variant="outline" style="margin-top: 10px" @click="addSelfToInvite">{{
|
||||
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
|
||||
>
|
||||
|
||||
<CheckIcon :class="['ml-auto size-4', selected ? 'opacity-100' : 'opacity-0']" />
|
||||
</div>
|
||||
</template>
|
||||
</VirtualCombobox>
|
||||
<div style="width: 100%; margin-top: 15px">
|
||||
<VirtualCombobox
|
||||
:model-value="Array.isArray(inviteDialog.userIds) ? inviteDialog.userIds : []"
|
||||
@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>
|
||||
|
||||
<template #footer>
|
||||
<Button
|
||||
variant="secondary"
|
||||
class="mr-2"
|
||||
:disabled="inviteDialog.loading || !inviteDialog.userIds.length"
|
||||
@click="showSendInviteDialog"
|
||||
>{{ t('dialog.invite.invite_with_message') }}</Button
|
||||
>
|
||||
<Button :disabled="inviteDialog.loading || !inviteDialog.userIds.length" @click="sendInvite">{{
|
||||
t('dialog.invite.invite')
|
||||
}}</Button>
|
||||
</template>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="secondary"
|
||||
class="mr-2"
|
||||
:disabled="inviteDialog.loading || !inviteDialog.userIds.length"
|
||||
@click="showSendInviteDialog"
|
||||
>{{ t('dialog.invite.invite_with_message') }}</Button
|
||||
>
|
||||
<Button :disabled="inviteDialog.loading || !inviteDialog.userIds.length" @click="sendInvite">{{
|
||||
t('dialog.invite.invite')
|
||||
}}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
<SendInviteDialog
|
||||
v-model:sendInviteDialogVisible="sendInviteDialogVisible"
|
||||
v-model:sendInviteDialog="sendInviteDialog"
|
||||
:invite-dialog="inviteDialog"
|
||||
@closeInviteDialog="closeInviteDialog" />
|
||||
</el-dialog>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, ref } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Check as CheckIcon } from 'lucide-vue-next';
|
||||
|
||||
@@ -1,27 +1,35 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="isSendInviteConfirmDialogVisible"
|
||||
:title="t('dialog.invite_message.header')"
|
||||
width="400px"
|
||||
append-to-body
|
||||
@close="cancelInviteConfirm">
|
||||
<div style="font-size: 12px">
|
||||
<span>{{ t('dialog.invite_message.confirmation') }}</span>
|
||||
</div>
|
||||
<Dialog
|
||||
:open="isSendInviteConfirmDialogVisible"
|
||||
@update:open="
|
||||
(open) => {
|
||||
if (!open) cancelInviteConfirm();
|
||||
}
|
||||
">
|
||||
<DialogContent class="x-dialog sm:max-w-100">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.invite_message.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<template #footer>
|
||||
<Button variant="secondary" @click="cancelInviteConfirm">
|
||||
{{ t('dialog.invite_message.cancel') }}
|
||||
</Button>
|
||||
<Button @click="sendInviteConfirm">
|
||||
{{ t('dialog.invite_message.confirm') }}
|
||||
</Button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<div style="font-size: 12px">
|
||||
<span>{{ t('dialog.invite_message.confirmation') }}</span>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" @click="cancelInviteConfirm">
|
||||
{{ t('dialog.invite_message.cancel') }}
|
||||
</Button>
|
||||
<Button @click="sendInviteConfirm">
|
||||
{{ t('dialog.invite_message.confirm') }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -1,55 +1,62 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="sendInviteDialogVisible"
|
||||
:title="t('dialog.invite_message.header')"
|
||||
width="800px"
|
||||
append-to-body
|
||||
@close="cancelSendInvite">
|
||||
<template v-if="isLocalUserVrcPlusSupporter">
|
||||
<!-- <template v-if="gallerySelectDialog.selectedFileId">-->
|
||||
<!-- <div style="display: inline-block; flex: none; margin-right: 5px">-->
|
||||
<!-- <el-popover placement="right" :width="500px" trigger="click">-->
|
||||
<!-- <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>
|
||||
<Dialog
|
||||
:open="sendInviteDialogVisible"
|
||||
@update:open="
|
||||
(open) => {
|
||||
if (!open) cancelSendInvite();
|
||||
}
|
||||
">
|
||||
<DialogContent class="x-dialog sm:max-w-200">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.invite_message.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px"
|
||||
:table="inviteMessageTanstackTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleInviteMessageRowClick" />
|
||||
<template v-if="isLocalUserVrcPlusSupporter">
|
||||
<!-- <template v-if="gallerySelectDialog.selectedFileId">-->
|
||||
<!-- <div style="display: inline-block; flex: none; margin-right: 5px">-->
|
||||
<!-- <el-popover placement="right" :width="500px" trigger="click">-->
|
||||
<!-- <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
|
||||
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
|
||||
v-model:isSendInviteConfirmDialogVisible="isSendInviteConfirmDialogVisible"
|
||||
:sendInviteDialog="sendInviteDialog"
|
||||
@@ -62,10 +69,11 @@
|
||||
@update:sendInviteDialog="emit('update:sendInviteDialog', $event)"
|
||||
:invite-dialog="inviteDialog"
|
||||
@closeInviteDialog="closeInviteDialog" />
|
||||
</el-dialog>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, ref } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DataTableLayout } from '@/components/ui/data-table';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:z-index="inviteGroupDialogIndex"
|
||||
v-model="inviteGroupDialog.visible"
|
||||
:title="t('dialog.invite_to_group.header')"
|
||||
width="450px"
|
||||
append-to-body>
|
||||
<div v-if="inviteGroupDialog.visible" v-loading="inviteGroupDialog.loading">
|
||||
<Dialog v-model:open="inviteGroupDialog.visible">
|
||||
<DialogContent class="sm:max-w-112.5">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.invite_to_group.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div v-if="inviteGroupDialog.visible" v-loading="inviteGroupDialog.loading">
|
||||
<span>{{ t('dialog.invite_to_group.description') }}</span>
|
||||
<br />
|
||||
|
||||
@@ -64,20 +64,25 @@
|
||||
</template>
|
||||
</VirtualCombobox>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button
|
||||
:disabled="inviteGroupDialog.loading || !inviteGroupDialog.userIds.length || !inviteGroupDialog.groupId"
|
||||
@click="sendGroupInvite">
|
||||
{{ t('dialog.invite_to_group.invite') }}
|
||||
</Button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
:disabled="
|
||||
inviteGroupDialog.loading || !inviteGroupDialog.userIds.length || !inviteGroupDialog.groupId
|
||||
"
|
||||
@click="sendGroupInvite">
|
||||
{{ t('dialog.invite_to_group.invite') }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import { computed, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Check as CheckIcon } from 'lucide-vue-next';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { toast } from 'vue-sonner';
|
||||
@@ -87,7 +92,6 @@
|
||||
import { useFriendStore, useGroupStore, useModalStore } from '../../stores';
|
||||
import { groupRequest, userRequest } from '../../api';
|
||||
import { VirtualCombobox } from '../ui/virtual-combobox';
|
||||
import { getNextDialogIndex } from '../../shared/utils/base/ui';
|
||||
|
||||
import configRepository from '../../service/config';
|
||||
|
||||
@@ -109,8 +113,6 @@
|
||||
}
|
||||
);
|
||||
|
||||
const inviteGroupDialogIndex = ref(2000);
|
||||
|
||||
const groupsWithInvitePermission = computed(() => {
|
||||
return Array.from(currentUserGroups.value.values()).filter((group) =>
|
||||
hasGroupPermission(group, 'group-invites-manage')
|
||||
@@ -223,9 +225,6 @@
|
||||
);
|
||||
|
||||
function initDialog() {
|
||||
nextTick(() => {
|
||||
inviteGroupDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
const D = inviteGroupDialog.value;
|
||||
if (D.groupId) {
|
||||
groupRequest
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<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">
|
||||
<Field>
|
||||
<FieldLabel>{{ t('dialog.launch.url') }}</FieldLabel>
|
||||
@@ -63,8 +67,7 @@
|
||||
</FieldContent>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<template #footer>
|
||||
<div class="flex justify-end">
|
||||
<DialogFooter>
|
||||
<Button
|
||||
class="mr-1.5"
|
||||
variant="outline"
|
||||
@@ -117,14 +120,16 @@
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</template>
|
||||
<InviteDialog :invite-dialog="inviteDialog" @closeInviteDialog="closeInviteDialog" />
|
||||
</el-dialog>
|
||||
</DialogFooter>
|
||||
|
||||
<InviteDialog :invite-dialog="inviteDialog" @closeInviteDialog="closeInviteDialog" />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<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 {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -150,7 +155,6 @@
|
||||
} from '../../stores';
|
||||
import { checkCanInvite, getLaunchURL, isRealInstance, parseLocation } from '../../shared/utils';
|
||||
import { instanceRequest, worldRequest } from '../../api';
|
||||
import { getNextDialogIndex } from '../../shared/utils/base/ui';
|
||||
|
||||
import InviteDialog from './InviteDialog/InviteDialog.vue';
|
||||
import configRepository from '../../service/config';
|
||||
@@ -171,8 +175,6 @@
|
||||
launchDialog.value.desktop ? t('dialog.launch.start_as_desktop') : t('dialog.launch.launch')
|
||||
);
|
||||
|
||||
const launchDialogIndex = ref(2000);
|
||||
|
||||
let launchAsDesktopTimeoutId;
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
@@ -312,9 +314,6 @@
|
||||
if (!isRealInstance(tag)) {
|
||||
return;
|
||||
}
|
||||
nextTick(() => {
|
||||
launchDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
const D = launchDialog.value;
|
||||
D.tag = tag;
|
||||
D.secureOrShortName = shortName;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:z-index="moderateGroupDialogIndex"
|
||||
v-model="moderateGroupDialog.visible"
|
||||
:title="t('dialog.moderate_group.header')"
|
||||
width="450px"
|
||||
append-to-body>
|
||||
<div v-if="moderateGroupDialog.visible">
|
||||
<Dialog v-model:open="moderateGroupDialog.visible">
|
||||
<DialogContent class="sm:max-w-112.5">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.moderate_group.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div v-if="moderateGroupDialog.visible">
|
||||
<div class="x-friend-item" style="cursor: default">
|
||||
<div class="avatar">
|
||||
<img :src="userImage(moderateGroupDialog.userObject)" loading="lazy" />
|
||||
@@ -37,30 +37,32 @@
|
||||
</template>
|
||||
</VirtualCombobox>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button
|
||||
:disabled="!moderateGroupDialog.userId || !moderateGroupDialog.groupId"
|
||||
@click="
|
||||
showGroupMemberModerationDialog(moderateGroupDialog.groupId, moderateGroupDialog.userId);
|
||||
moderateGroupDialog.visible = false;
|
||||
">
|
||||
{{ t('dialog.moderate_group.moderation_tools') }}
|
||||
</Button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
:disabled="!moderateGroupDialog.userId || !moderateGroupDialog.groupId"
|
||||
@click="
|
||||
showGroupMemberModerationDialog(moderateGroupDialog.groupId, moderateGroupDialog.userId);
|
||||
moderateGroupDialog.visible = false;
|
||||
">
|
||||
{{ t('dialog.moderate_group.moderation_tools') }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import { computed, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { groupRequest, userRequest } from '../../api';
|
||||
import { hasGroupModerationPermission, userImage } from '../../shared/utils';
|
||||
import { VirtualCombobox } from '../ui/virtual-combobox';
|
||||
import { getNextDialogIndex } from '../../shared/utils/base/ui';
|
||||
import { useGroupStore } from '../../stores';
|
||||
|
||||
const { currentUserGroups, moderateGroupDialog } = storeToRefs(useGroupStore());
|
||||
@@ -99,12 +101,7 @@
|
||||
}
|
||||
);
|
||||
|
||||
const moderateGroupDialogIndex = ref(2000);
|
||||
|
||||
function initDialog() {
|
||||
nextTick(() => {
|
||||
moderateGroupDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
const D = moderateGroupDialog.value;
|
||||
if (D.groupId) {
|
||||
groupRequest
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:z-index="newInstanceDialogIndex"
|
||||
v-model="newInstanceDialog.visible"
|
||||
:title="t('dialog.new_instance.header')"
|
||||
width="650px"
|
||||
append-to-body>
|
||||
<Dialog v-model:open="newInstanceDialog.visible">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.new_instance.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<TabsUnderline
|
||||
v-model="newInstanceDialog.selectedTab"
|
||||
:items="newInstanceTabs"
|
||||
@@ -435,7 +434,7 @@
|
||||
</FieldGroup>
|
||||
</template>
|
||||
</TabsUnderline>
|
||||
<template v-if="newInstanceDialog.selectedTab === 'Normal'" #footer>
|
||||
<DialogFooter v-if="newInstanceDialog.selectedTab === 'Normal'">
|
||||
<template v-if="newInstanceDialog.instanceCreated">
|
||||
<Button variant="outline" class="mr-2" @click="copyInstanceUrl(newInstanceDialog.location)">{{
|
||||
t('dialog.new_instance.copy_url')
|
||||
@@ -473,8 +472,8 @@
|
||||
<template v-else>
|
||||
<Button @click="handleCreateNewInstance">{{ t('dialog.new_instance.create_instance') }}</Button>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="newInstanceDialog.selectedTab === 'Legacy'" #footer>
|
||||
</DialogFooter>
|
||||
<DialogFooter v-else-if="newInstanceDialog.selectedTab === 'Legacy'">
|
||||
<Button variant="outline" class="mr-2" @click="copyInstanceUrl(newInstanceDialog.location)">{{
|
||||
t('dialog.new_instance.copy_url')
|
||||
}}</Button>
|
||||
@@ -506,14 +505,17 @@
|
||||
t('dialog.new_instance.launch')
|
||||
}}</Button>
|
||||
</template>
|
||||
</template>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
<InviteDialog :invite-dialog="inviteDialog" @closeInviteDialog="closeInviteDialog" />
|
||||
</el-dialog>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
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 { Check as CheckIcon } from 'lucide-vue-next';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
@@ -545,7 +547,6 @@
|
||||
import { groupRequest, instanceRequest, worldRequest } from '../../api';
|
||||
import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group';
|
||||
import { VirtualCombobox } from '../ui/virtual-combobox';
|
||||
import { getNextDialogIndex } from '../../shared/utils/base/ui';
|
||||
|
||||
import InviteDialog from './InviteDialog/InviteDialog.vue';
|
||||
import configRepository from '../../service/config';
|
||||
@@ -572,8 +573,6 @@
|
||||
const { currentUser, isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
|
||||
const { canOpenInstanceInGame } = useInviteStore();
|
||||
|
||||
const newInstanceDialogIndex = ref(2000);
|
||||
|
||||
const newInstanceDialog = ref({
|
||||
visible: false,
|
||||
// loading: false,
|
||||
@@ -778,9 +777,6 @@
|
||||
if (!isRealInstance(tag)) {
|
||||
return;
|
||||
}
|
||||
nextTick(() => {
|
||||
newInstanceDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
const D = newInstanceDialog.value;
|
||||
const L = parseLocation(tag);
|
||||
if (D.worldId === L.worldId) {
|
||||
|
||||
@@ -1,34 +1,36 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:z-index="previousInstancesGroupDialogIndex"
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.previous_instances.header')"
|
||||
width="1000px"
|
||||
append-to-body>
|
||||
<DataTableLayout
|
||||
class="min-w-0 w-full"
|
||||
:table="table"
|
||||
:loading="loading"
|
||||
:table-style="tableStyle"
|
||||
:page-sizes="pageSizes"
|
||||
:total-items="totalItems"
|
||||
:on-page-size-change="handlePageSizeChange">
|
||||
<template #toolbar>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<span style="font-size: 14px" v-text="previousInstancesGroupDialog.groupRef.name"></span>
|
||||
<InputGroupField
|
||||
class="w-1/3"
|
||||
v-model="search"
|
||||
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
||||
clearable />
|
||||
</div>
|
||||
</template>
|
||||
</DataTableLayout>
|
||||
</el-dialog>
|
||||
<Dialog v-model:open="isVisible">
|
||||
<DialogContent class="sm:max-w-250">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.previous_instances.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DataTableLayout
|
||||
class="min-w-0 w-full"
|
||||
:table="table"
|
||||
:loading="loading"
|
||||
:table-style="tableStyle"
|
||||
:page-sizes="pageSizes"
|
||||
:total-items="totalItems"
|
||||
:on-page-size-change="handlePageSizeChange">
|
||||
<template #toolbar>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<span style="font-size: 14px" v-text="previousInstancesGroupDialog.groupRef.name"></span>
|
||||
<InputGroupField
|
||||
class="w-1/3"
|
||||
v-model="search"
|
||||
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
||||
clearable />
|
||||
</div>
|
||||
</template>
|
||||
</DataTableLayout>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<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 { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -44,7 +46,6 @@
|
||||
import { DataTableLayout } from '../../ui/data-table';
|
||||
import { createColumns } from './previousInstancesGroupColumns.jsx';
|
||||
import { database } from '../../../service/database';
|
||||
import { getNextDialogIndex } from '../../../shared/utils/base/ui';
|
||||
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
|
||||
|
||||
const { showPreviousInstancesInfoDialog } = useInstanceStore();
|
||||
@@ -52,7 +53,6 @@
|
||||
const { stringComparer } = storeToRefs(useSearchStore());
|
||||
const { t } = useI18n();
|
||||
|
||||
const previousInstancesGroupDialogIndex = ref(2000);
|
||||
const loading = ref(false);
|
||||
|
||||
const modalStore = useModalStore();
|
||||
@@ -140,9 +140,6 @@
|
||||
() => props.previousInstancesGroupDialog.openFlg,
|
||||
() => {
|
||||
if (props.previousInstancesGroupDialog.visible) {
|
||||
nextTick(() => {
|
||||
previousInstancesGroupDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
refreshPreviousInstancesGroupTable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,41 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:z-index="previousInstancesInfoDialogIndex"
|
||||
:model-value="previousInstancesInfoDialogVisible"
|
||||
:title="t('dialog.previous_instances.info')"
|
||||
width="800px"
|
||||
:fullscreen="fullscreen"
|
||||
destroy-on-close
|
||||
@close="closeDialog">
|
||||
<DataTableLayout
|
||||
class="min-w-0 w-full"
|
||||
:table="table"
|
||||
:loading="loading"
|
||||
:table-style="tableStyle"
|
||||
:page-sizes="pageSizes"
|
||||
:total-items="totalItems"
|
||||
:on-page-size-change="handlePageSizeChange">
|
||||
<template #toolbar>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<Location :location="location.tag" style="font-size: 14px" />
|
||||
<InputGroupField
|
||||
v-model="search"
|
||||
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
||||
style="width: 150px"
|
||||
clearable />
|
||||
</div>
|
||||
</template>
|
||||
</DataTableLayout>
|
||||
</el-dialog>
|
||||
<Dialog
|
||||
:open="previousInstancesInfoDialogVisible"
|
||||
@update:open="
|
||||
(open) => {
|
||||
if (!open) closeDialog();
|
||||
}
|
||||
">
|
||||
<DialogContent class="sm:max-w-200">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.previous_instances.info') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DataTableLayout
|
||||
class="min-w-0 w-full"
|
||||
:table="table"
|
||||
:loading="loading"
|
||||
:table-style="tableStyle"
|
||||
:page-sizes="pageSizes"
|
||||
:total-items="totalItems"
|
||||
:on-page-size-change="handlePageSizeChange">
|
||||
<template #toolbar>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<Location :location="location.tag" style="font-size: 14px" />
|
||||
<InputGroupField
|
||||
v-model="search"
|
||||
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
||||
style="width: 150px"
|
||||
clearable />
|
||||
</div>
|
||||
</template>
|
||||
</DataTableLayout>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -40,7 +46,6 @@
|
||||
import { InputGroupField } from '../../../components/ui/input-group';
|
||||
import { createColumns } from './previousInstancesInfoColumns.jsx';
|
||||
import { database } from '../../../service/database';
|
||||
import { getNextDialogIndex } from '../../../shared/utils/base/ui';
|
||||
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
|
||||
|
||||
const { lookupUser } = useUserStore();
|
||||
@@ -49,8 +54,6 @@
|
||||
const { gameLogIsFriend, gameLogIsFavorite } = useGameLogStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const previousInstancesInfoDialogIndex = ref(2000);
|
||||
|
||||
const loading = ref(false);
|
||||
const rawRows = ref([]);
|
||||
const search = ref('');
|
||||
@@ -147,7 +150,6 @@
|
||||
);
|
||||
|
||||
function init() {
|
||||
previousInstancesInfoDialogIndex.value = getNextDialogIndex();
|
||||
loading.value = true;
|
||||
location.value = parseLocation(previousInstancesInfoDialogInstanceId.value);
|
||||
}
|
||||
|
||||
@@ -1,35 +1,37 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:z-index="previousInstancesWorldDialogIndex"
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.previous_instances.header')"
|
||||
width="1000px"
|
||||
append-to-body>
|
||||
<DataTableLayout
|
||||
class="min-w-0 w-full"
|
||||
:table="table"
|
||||
:loading="loading"
|
||||
:table-style="tableStyle"
|
||||
:page-sizes="pageSizes"
|
||||
:total-items="totalItems"
|
||||
:on-page-size-change="handlePageSizeChange">
|
||||
<template #toolbar>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<span style="font-size: 14px" v-text="previousInstancesWorldDialog.worldRef.name"></span>
|
||||
<InputGroupField
|
||||
v-model="search"
|
||||
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
||||
clearable
|
||||
class="w-1/3"
|
||||
style="display: block" />
|
||||
</div>
|
||||
</template>
|
||||
</DataTableLayout>
|
||||
</el-dialog>
|
||||
<Dialog v-model:open="isVisible">
|
||||
<DialogContent class="sm:max-w-250">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.previous_instances.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DataTableLayout
|
||||
class="min-w-0 w-full"
|
||||
:table="table"
|
||||
:loading="loading"
|
||||
:table-style="tableStyle"
|
||||
:page-sizes="pageSizes"
|
||||
:total-items="totalItems"
|
||||
:on-page-size-change="handlePageSizeChange">
|
||||
<template #toolbar>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<span style="font-size: 14px" v-text="previousInstancesWorldDialog.worldRef.name"></span>
|
||||
<InputGroupField
|
||||
v-model="search"
|
||||
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
||||
clearable
|
||||
class="w-1/3"
|
||||
style="display: block" />
|
||||
</div>
|
||||
</template>
|
||||
</DataTableLayout>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<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 { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -52,7 +54,6 @@
|
||||
import { DataTableLayout } from '../../ui/data-table';
|
||||
import { createColumns } from './previousInstancesWorldColumns.jsx';
|
||||
import { database } from '../../../service/database';
|
||||
import { getNextDialogIndex } from '../../../shared/utils/base/ui';
|
||||
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -79,7 +80,6 @@
|
||||
const pageSize = ref(10);
|
||||
const tableStyle = { maxHeight: '400px' };
|
||||
const loading = ref(false);
|
||||
const previousInstancesWorldDialogIndex = ref(2000);
|
||||
|
||||
const isVisible = computed({
|
||||
get: () => props.previousInstancesWorldDialog.visible,
|
||||
@@ -188,9 +188,6 @@
|
||||
() => props.previousInstancesWorldDialog.openFlg,
|
||||
() => {
|
||||
if (props.previousInstancesWorldDialog.visible) {
|
||||
nextTick(() => {
|
||||
previousInstancesWorldDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
refreshPreviousInstancesWorldTable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
v-model="sendBoopDialog.visible"
|
||||
:title="t('dialog.boop_dialog.header')"
|
||||
width="450px"
|
||||
@close="closeDialog">
|
||||
<Dialog v-model:open="sendBoopDialog.visible">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.boop_dialog.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<span>{{ displayName }}</span>
|
||||
|
||||
<br />
|
||||
@@ -58,7 +57,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<DialogFooter>
|
||||
<Button size="sm" variant="outline" class="mr-2" @click="showGalleryPage">{{
|
||||
t('dialog.boop_dialog.emoji_manager')
|
||||
}}</Button>
|
||||
@@ -68,14 +67,16 @@
|
||||
<Button size="sm" :disabled="!sendBoopDialog.userId" @click="sendBoop">{{
|
||||
t('dialog.boop_dialog.send')
|
||||
}}</Button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Check as CheckIcon } from 'lucide-vue-next';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
|
||||
@@ -1,56 +1,58 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
v-model="bioDialog.visible"
|
||||
:title="t('dialog.bio.header')"
|
||||
width="600px"
|
||||
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 />
|
||||
<Dialog v-model:open="bioDialog.visible">
|
||||
<DialogContent class="x-dialog sm:max-w-150">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.bio.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<InputGroupAction
|
||||
v-for="(link, index) in bioDialog.bioLinks"
|
||||
:key="index"
|
||||
v-model="bioDialog.bioLinks[index]"
|
||||
:maxlength="64"
|
||||
show-count
|
||||
size="sm"
|
||||
style="margin-top: 5px">
|
||||
<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>
|
||||
<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 />
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
:disabled="bioDialog.bioLinks.length >= 3"
|
||||
size="sm"
|
||||
class="mt-2"
|
||||
@click="bioDialog.bioLinks.push('')">
|
||||
{{ t('dialog.bio.add_link') }}
|
||||
</Button>
|
||||
</div>
|
||||
<InputGroupAction
|
||||
v-for="(link, index) in bioDialog.bioLinks"
|
||||
:key="index"
|
||||
v-model="bioDialog.bioLinks[index]"
|
||||
:maxlength="64"
|
||||
show-count
|
||||
size="sm"
|
||||
style="margin-top: 5px">
|
||||
<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 :disabled="bioDialog.loading" @click="saveBio">
|
||||
{{ t('dialog.bio.update') }}
|
||||
</Button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<Button
|
||||
variant="outline"
|
||||
:disabled="bioDialog.bioLinks.length >= 3"
|
||||
size="sm"
|
||||
class="mt-2"
|
||||
@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>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { InputGroupAction, InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Trash2 } from 'lucide-vue-next';
|
||||
|
||||
@@ -1,45 +1,49 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="props.visible"
|
||||
title="Edit Note And Memo"
|
||||
:show-close="false"
|
||||
top="30vh"
|
||||
width="500px"
|
||||
append-to-body
|
||||
@close="cancel">
|
||||
<template v-if="!hideUserNotes || (hideUserNotes && hideUserMemos)">
|
||||
<span class="name my-2">{{ t('dialog.user.info.note') }}</span>
|
||||
<br />
|
||||
<InputGroupTextareaField
|
||||
v-model="note"
|
||||
:autosize="{ minRows: 6, maxRows: 20 }"
|
||||
:maxlength="256"
|
||||
:rows="6"
|
||||
:placeholder="t('dialog.user.info.note_placeholder')"
|
||||
input-class="extra resize-none"
|
||||
class="my-2"
|
||||
show-count />
|
||||
</template>
|
||||
<template v-if="!hideUserMemos || (hideUserNotes && hideUserMemos)">
|
||||
<span class="name">{{ t('dialog.user.info.memo') }}</span>
|
||||
<InputGroupTextareaField
|
||||
v-model="memo"
|
||||
class="extra mt-2"
|
||||
:rows="6"
|
||||
:placeholder="t('dialog.user.info.memo_placeholder')"
|
||||
input-class="resize-none min-h-0" />
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<Dialog
|
||||
:open="props.visible"
|
||||
@update:open="
|
||||
(open) => {
|
||||
if (!open) cancel();
|
||||
}
|
||||
">
|
||||
<DialogContent class="x-dialog sm:max-w-125 translate-y-0" style="top: 30vh" :show-close-button="false">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit Note And Memo</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<template v-if="!hideUserNotes || (hideUserNotes && hideUserMemos)">
|
||||
<span class="name my-2">{{ t('dialog.user.info.note') }}</span>
|
||||
<br />
|
||||
<InputGroupTextareaField
|
||||
v-model="note"
|
||||
:autosize="{ minRows: 6, maxRows: 20 }"
|
||||
:maxlength="256"
|
||||
:rows="6"
|
||||
:placeholder="t('dialog.user.info.note_placeholder')"
|
||||
input-class="extra resize-none"
|
||||
class="my-2"
|
||||
show-count />
|
||||
</template>
|
||||
<template v-if="!hideUserMemos || (hideUserNotes && hideUserMemos)">
|
||||
<span class="name">{{ t('dialog.user.info.memo') }}</span>
|
||||
<InputGroupTextareaField
|
||||
v-model="memo"
|
||||
class="extra mt-2"
|
||||
:rows="6"
|
||||
:placeholder="t('dialog.user.info.memo_placeholder')"
|
||||
input-class="resize-none min-h-0" />
|
||||
</template>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" @click="cancel" class="mr-2">Cancel</Button>
|
||||
<Button @click="saveChanges">Confirm</Button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
|
||||
@@ -1,63 +1,67 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
v-model="languageDialog.visible"
|
||||
:title="t('dialog.language.header')"
|
||||
width="400px"
|
||||
append-to-body>
|
||||
<div v-loading="languageDialog.loading">
|
||||
<div v-for="item in currentUser.$languages" :key="item.key" style="margin: 6px 0">
|
||||
<Badge variant="outline" style="margin-right: 5px">
|
||||
<span
|
||||
class="flags"
|
||||
:class="languageClass(item.key)"
|
||||
style="display: inline-block; margin-right: 5px"></span>
|
||||
{{ item.value }} ({{ item.key.toUpperCase() }})
|
||||
<button
|
||||
type="button"
|
||||
style="
|
||||
margin-left: 6px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
"
|
||||
@click="removeUserLanguage(item.key)">
|
||||
<X class="h-3 w-3" />
|
||||
</button>
|
||||
</Badge>
|
||||
<Dialog v-model:open="languageDialog.visible">
|
||||
<DialogContent class="x-dialog sm:max-w-100">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.language.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div v-loading="languageDialog.loading">
|
||||
<div v-for="item in currentUser.$languages" :key="item.key" style="margin: 6px 0">
|
||||
<Badge variant="outline" style="margin-right: 5px">
|
||||
<span
|
||||
class="flags"
|
||||
:class="languageClass(item.key)"
|
||||
style="display: inline-block; margin-right: 5px"></span>
|
||||
{{ item.value }} ({{ item.key.toUpperCase() }})
|
||||
<button
|
||||
type="button"
|
||||
style="
|
||||
margin-left: 6px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
"
|
||||
@click="removeUserLanguage(item.key)">
|
||||
<X class="h-3 w-3" />
|
||||
</button>
|
||||
</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>
|
||||
<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>
|
||||
</el-dialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { X } from 'lucide-vue-next';
|
||||
import { ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
@@ -1,35 +1,37 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:z-index="previousInstancesUserDialogIndex"
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.previous_instances.header')"
|
||||
width="1000px"
|
||||
append-to-body>
|
||||
<DataTableLayout
|
||||
class="min-w-0 w-full"
|
||||
:table="table"
|
||||
:loading="loading"
|
||||
:table-style="tableStyle"
|
||||
:page-sizes="pageSizes"
|
||||
:total-items="totalItems"
|
||||
:on-page-size-change="handlePageSizeChange">
|
||||
<template #toolbar>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<span style="font-size: 14px" v-text="previousInstancesUserDialog.userRef.displayName"></span>
|
||||
<InputGroupField
|
||||
v-model="search"
|
||||
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
||||
clearable
|
||||
class="w-1/3"
|
||||
style="display: block" />
|
||||
</div>
|
||||
</template>
|
||||
</DataTableLayout>
|
||||
</el-dialog>
|
||||
<Dialog v-model:open="isVisible">
|
||||
<DialogContent class="sm:max-w-250">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.previous_instances.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DataTableLayout
|
||||
class="min-w-0 w-full"
|
||||
:table="table"
|
||||
:loading="loading"
|
||||
:table-style="tableStyle"
|
||||
:page-sizes="pageSizes"
|
||||
:total-items="totalItems"
|
||||
:on-page-size-change="handlePageSizeChange">
|
||||
<template #toolbar>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<span style="font-size: 14px" v-text="previousInstancesUserDialog.userRef.displayName"></span>
|
||||
<InputGroupField
|
||||
v-model="search"
|
||||
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
||||
clearable
|
||||
class="w-1/3"
|
||||
style="display: block" />
|
||||
</div>
|
||||
</template>
|
||||
</DataTableLayout>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<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 { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -52,7 +54,6 @@
|
||||
import { DataTableLayout } from '../../ui/data-table';
|
||||
import { createColumns } from './previousInstancesUserColumns.jsx';
|
||||
import { database } from '../../../service/database';
|
||||
import { getNextDialogIndex } from '../../../shared/utils/base/ui';
|
||||
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
|
||||
|
||||
const props = defineProps({
|
||||
@@ -90,8 +91,6 @@
|
||||
const vrcxStore = useVrcxStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const previousInstancesUserDialogIndex = ref(2000);
|
||||
|
||||
const isVisible = computed({
|
||||
get: () => props.previousInstancesUserDialog.visible,
|
||||
set: (value) => {
|
||||
@@ -178,9 +177,6 @@
|
||||
() => props.previousInstancesUserDialog.openFlg,
|
||||
() => {
|
||||
if (props.previousInstancesUserDialog.visible) {
|
||||
nextTick(() => {
|
||||
previousInstancesUserDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
refreshPreviousInstancesUserTable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
v-model="pronounsDialog.visible"
|
||||
:title="t('dialog.pronouns.header')"
|
||||
width="600px"
|
||||
append-to-body>
|
||||
<div v-loading="pronounsDialog.loading">
|
||||
<InputGroupTextareaField
|
||||
v-model="pronounsDialog.pronouns"
|
||||
:maxlength="32"
|
||||
:rows="2"
|
||||
:placeholder="t('dialog.pronouns.pronouns_placeholder')"
|
||||
show-count />
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button :disabled="pronounsDialog.loading" @click="savePronouns">
|
||||
{{ t('dialog.pronouns.update') }}
|
||||
</Button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<Dialog v-model:open="pronounsDialog.visible">
|
||||
<DialogContent class="x-dialog sm:max-w-150">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.pronouns.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div v-loading="pronounsDialog.loading">
|
||||
<InputGroupTextareaField
|
||||
v-model="pronounsDialog.pronouns"
|
||||
:maxlength="32"
|
||||
:rows="2"
|
||||
:placeholder="t('dialog.pronouns.pronouns_placeholder')"
|
||||
show-count />
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button :disabled="pronounsDialog.loading" @click="savePronouns">
|
||||
{{ t('dialog.pronouns.update') }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
import { toast } from 'vue-sonner';
|
||||
|
||||
@@ -1,30 +1,37 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="sendInviteRequestDialogVisible"
|
||||
:title="t('dialog.invite_request_message.header')"
|
||||
width="800px"
|
||||
append-to-body
|
||||
@close="cancelSendInviteRequest">
|
||||
<template v-if="isLocalUserVrcPlusSupporter">
|
||||
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
|
||||
</template>
|
||||
<Dialog
|
||||
:open="sendInviteRequestDialogVisible"
|
||||
@update:open="
|
||||
(open) => {
|
||||
if (!open) cancelSendInviteRequest();
|
||||
}
|
||||
">
|
||||
<DialogContent class="x-dialog sm:max-w-200">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.invite_request_message.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px"
|
||||
:table="inviteRequestMessageTanstackTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleInviteRequestMessageRowClick" />
|
||||
<template v-if="isLocalUserVrcPlusSupporter">
|
||||
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
|
||||
</template>
|
||||
|
||||
<DataTableLayout
|
||||
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
|
||||
v-model:isSendInviteConfirmDialogVisible="isSendInviteConfirmDialogVisible"
|
||||
:sendInviteDialog="sendInviteDialog"
|
||||
@@ -37,10 +44,11 @@
|
||||
@update:sendInviteDialog="emit('update:sendInviteDialog', $event)"
|
||||
:invite-dialog="inviteDialog"
|
||||
@closeInviteDialog="closeInviteDialog" />
|
||||
</el-dialog>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, ref } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DataTableLayout } from '@/components/ui/data-table';
|
||||
|
||||
@@ -1,96 +1,98 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
v-model="socialStatusDialog.visible"
|
||||
:title="t('dialog.social_status.header')"
|
||||
append-to-body
|
||||
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>
|
||||
<Dialog v-model:open="socialStatusDialog.visible">
|
||||
<DialogContent class="x-dialog sm:max-w-100">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.social_status.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<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
|
||||
v-if="!isOpen && latestHistoryItem"
|
||||
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 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
|
||||
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>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</div>
|
||||
<div
|
||||
v-if="!isOpen && latestHistoryItem"
|
||||
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>
|
||||
<Button :disabled="socialStatusDialog.loading" @click="saveSocialStatus">
|
||||
{{ t('dialog.social_status.update') }}
|
||||
</Button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<DialogFooter>
|
||||
<Button :disabled="socialStatusDialog.loading" @click="saveSocialStatus">
|
||||
{{ t('dialog.social_status.update') }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
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 { computed, ref } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InputGroupField } from '@/components/ui/input-group';
|
||||
import { ChevronsUpDown } from 'lucide-vue-next';
|
||||
import { InputGroupField } from '@/components/ui/input-group';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,10 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:z-index="VRCXUpdateDialogIndex"
|
||||
class="x-dialog"
|
||||
v-model="VRCXUpdateDialog.visible"
|
||||
:title="t('dialog.vrcx_updater.header')"
|
||||
append-to-body
|
||||
width="400px">
|
||||
<div v-loading="checkingForVRCXUpdate" style="margin-top: 15px">
|
||||
<Dialog v-model:open="VRCXUpdateDialog.visible">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.vrcx_updater.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div v-loading="checkingForVRCXUpdate" style="margin-top: 15px">
|
||||
<template v-if="updateInProgress">
|
||||
<Progress :model-value="updateProgress" class="w-full" />
|
||||
<div class="mt-2 text-xs" v-text="updateProgressText()"></div>
|
||||
@@ -61,9 +59,9 @@
|
||||
<span>{{ t('dialog.vrcx_updater.latest_version') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" class="mr-2" v-if="updateInProgress" @click="cancelUpdate">
|
||||
{{ t('dialog.vrcx_updater.cancel') }}
|
||||
</Button>
|
||||
@@ -77,15 +75,16 @@
|
||||
<Button variant="default" v-if="!updateInProgress && pendingVRCXInstall" @click="restartVRCX(true)">
|
||||
{{ t('dialog.vrcx_updater.install') }}
|
||||
</Button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { nextTick, ref, watch } from 'vue';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { AlertCircle } from 'lucide-vue-next';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
@@ -93,7 +92,6 @@
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
|
||||
import { getNextDialogIndex } from '../../shared/utils/base/ui';
|
||||
import { useVRCXUpdaterStore } from '../../stores';
|
||||
|
||||
const VRCXUpdaterStore = useVRCXUpdaterStore();
|
||||
@@ -111,7 +109,6 @@
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const VRCXUpdateDialogIndex = ref(2000);
|
||||
const handleBranchChange = (value) => {
|
||||
if (!value || value === branch.value) {
|
||||
return;
|
||||
@@ -119,15 +116,4 @@
|
||||
branch.value = value;
|
||||
loadBranchVersions();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => VRCXUpdateDialog,
|
||||
(newVal) => {
|
||||
if (newVal.value.visible) {
|
||||
nextTick(() => {
|
||||
VRCXUpdateDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -1,33 +1,40 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="changeWorldImageDialogVisible"
|
||||
:title="t('dialog.change_content_image.world')"
|
||||
width="850px"
|
||||
append-to-body
|
||||
@close="closeDialog">
|
||||
<div>
|
||||
<input
|
||||
id="WorldImageUploadButton"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
style="display: none"
|
||||
@change="onFileChangeWorldImage" />
|
||||
<span>{{ t('dialog.change_content_image.description') }}</span>
|
||||
<br />
|
||||
<Button variant="outline" size="sm" :disabled="changeWorldImageDialogLoading" @click="uploadWorldImage">
|
||||
<Upload />
|
||||
{{ t('dialog.change_content_image.upload') }}
|
||||
</Button>
|
||||
<br />
|
||||
<div class="x-change-image-item">
|
||||
<img :src="previousImageUrl" class="img-size" loading="lazy" />
|
||||
<Dialog
|
||||
:open="changeWorldImageDialogVisible"
|
||||
@update:open="
|
||||
(open) => {
|
||||
if (!open) closeDialog();
|
||||
}
|
||||
">
|
||||
<DialogContent class="x-dialog sm:max-w-212.5">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.change_content_image.world') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div>
|
||||
<input
|
||||
id="WorldImageUploadButton"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
style="display: none"
|
||||
@change="onFileChangeWorldImage" />
|
||||
<span>{{ t('dialog.change_content_image.description') }}</span>
|
||||
<br />
|
||||
<Button variant="outline" size="sm" :disabled="changeWorldImageDialogLoading" @click="uploadWorldImage">
|
||||
<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>
|
||||
</el-dialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Upload } from 'lucide-vue-next';
|
||||
import { ref } from 'vue';
|
||||
|
||||
@@ -1,107 +1,110 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.set_world_tags.header')"
|
||||
width="400px"
|
||||
destroy-on-close
|
||||
append-to-body>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setWorldTagsDialog.avatarScalingDisabled" />
|
||||
<span>{{ t('dialog.set_world_tags.avatar_scaling_disabled') }}</span>
|
||||
</label>
|
||||
<br />
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setWorldTagsDialog.focusViewDisabled" />
|
||||
<span>{{ t('dialog.set_world_tags.focus_view_disabled') }}</span>
|
||||
</label>
|
||||
<br />
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setWorldTagsDialog.debugAllowed" />
|
||||
<span>{{ t('dialog.set_world_tags.enable_debugging') }}</span>
|
||||
</label>
|
||||
<div style="font-size: 12px; margin-top: 10px">{{ t('dialog.set_world_tags.author_tags') }}<br /></div>
|
||||
<InputGroupTextareaField
|
||||
v-model="setWorldTagsDialog.authorTags"
|
||||
:rows="2"
|
||||
placeholder=""
|
||||
style="margin-top: 10px"
|
||||
input-class="resize-none" />
|
||||
<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">
|
||||
<Checkbox v-model="setWorldTagsDialog.contentHorror" />
|
||||
<span>{{ t('dialog.set_world_tags.content_horror') }}</span>
|
||||
</label>
|
||||
<br />
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setWorldTagsDialog.contentGore" />
|
||||
<span>{{ t('dialog.set_world_tags.content_gore') }}</span>
|
||||
</label>
|
||||
<br />
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setWorldTagsDialog.contentViolence" />
|
||||
<span>{{ t('dialog.set_world_tags.content_violence') }}</span>
|
||||
</label>
|
||||
<br />
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setWorldTagsDialog.contentAdult" />
|
||||
<span>{{ t('dialog.set_world_tags.content_adult') }}</span>
|
||||
</label>
|
||||
<br />
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setWorldTagsDialog.contentSex" />
|
||||
<span>{{ t('dialog.set_world_tags.content_sex') }}</span>
|
||||
</label>
|
||||
<div style="font-size: 12px; margin-top: 10px">
|
||||
{{ 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>
|
||||
<Dialog v-model:open="isVisible">
|
||||
<DialogContent class="sm:max-w-100">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.set_world_tags.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setWorldTagsDialog.avatarScalingDisabled" />
|
||||
<span>{{ t('dialog.set_world_tags.avatar_scaling_disabled') }}</span>
|
||||
</label>
|
||||
<br />
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setWorldTagsDialog.focusViewDisabled" />
|
||||
<span>{{ t('dialog.set_world_tags.focus_view_disabled') }}</span>
|
||||
</label>
|
||||
<br />
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setWorldTagsDialog.debugAllowed" />
|
||||
<span>{{ t('dialog.set_world_tags.enable_debugging') }}</span>
|
||||
</label>
|
||||
<div style="font-size: 12px; margin-top: 10px">{{ t('dialog.set_world_tags.author_tags') }}<br /></div>
|
||||
<InputGroupTextareaField
|
||||
v-model="setWorldTagsDialog.authorTags"
|
||||
:rows="2"
|
||||
placeholder=""
|
||||
style="margin-top: 10px"
|
||||
input-class="resize-none" />
|
||||
<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">
|
||||
<Checkbox v-model="setWorldTagsDialog.contentHorror" />
|
||||
<span>{{ t('dialog.set_world_tags.content_horror') }}</span>
|
||||
</label>
|
||||
<br />
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setWorldTagsDialog.contentGore" />
|
||||
<span>{{ t('dialog.set_world_tags.content_gore') }}</span>
|
||||
</label>
|
||||
<br />
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setWorldTagsDialog.contentViolence" />
|
||||
<span>{{ t('dialog.set_world_tags.content_violence') }}</span>
|
||||
</label>
|
||||
<br />
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setWorldTagsDialog.contentAdult" />
|
||||
<span>{{ t('dialog.set_world_tags.content_adult') }}</span>
|
||||
</label>
|
||||
<br />
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<Checkbox v-model="setWorldTagsDialog.contentSex" />
|
||||
<span>{{ t('dialog.set_world_tags.content_sex') }}</span>
|
||||
</label>
|
||||
<div style="font-size: 12px; margin-top: 10px">
|
||||
{{ t('dialog.set_world_tags.default_content_settings') }}<br />
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
|
||||
@@ -1,34 +1,37 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.allowed_video_player_domains.header')"
|
||||
width="600px"
|
||||
destroy-on-close
|
||||
append-to-body>
|
||||
<div>
|
||||
<InputGroupAction
|
||||
v-for="(domain, index) in urlList"
|
||||
:key="index"
|
||||
v-model="urlList[index]"
|
||||
size="sm"
|
||||
style="margin-top: 5px">
|
||||
<template #actions>
|
||||
<Button variant="ghost" @click="urlList.splice(index, 1)"><Trash2 /></Button>
|
||||
</template>
|
||||
</InputGroupAction>
|
||||
<Button size="sm" variant="outline" style="margin-top: 5px" @click="urlList.push('')">
|
||||
{{ t('dialog.allowed_video_player_domains.add_domain') }}
|
||||
</Button>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button :disabled="!worldAllowedDomainsDialog.worldId" @click="saveWorldAllowedDomains">
|
||||
{{ t('dialog.allowed_video_player_domains.save') }}
|
||||
</Button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<Dialog v-model:open="isVisible">
|
||||
<DialogContent class="sm:max-w-150">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.allowed_video_player_domains.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div>
|
||||
<InputGroupAction
|
||||
v-for="(domain, index) in urlList"
|
||||
:key="index"
|
||||
v-model="urlList[index]"
|
||||
size="sm"
|
||||
style="margin-top: 5px">
|
||||
<template #actions>
|
||||
<Button variant="ghost" @click="urlList.splice(index, 1)"><Trash2 /></Button>
|
||||
</template>
|
||||
</InputGroupAction>
|
||||
<Button size="sm" variant="outline" style="margin-top: 5px" @click="urlList.push('')">
|
||||
{{ t('dialog.allowed_video_player_domains.add_domain') }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button :disabled="!worldAllowedDomainsDialog.worldId" @click="saveWorldAllowedDomains">
|
||||
{{ t('dialog.allowed_video_player_domains.save') }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InputGroupAction } from '@/components/ui/input-group';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -44,101 +44,105 @@
|
||||
<span>{{ t('view.charts.mutual_friend.progress.no_relationships_discovered') }}</span>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="isForceDialogVisible"
|
||||
:title="t('view.charts.mutual_friend.force_dialog.title')"
|
||||
width="440px">
|
||||
<p class="mutual-graph__force-description">
|
||||
{{ 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>
|
||||
<Dialog v-model:open="isForceDialogVisible">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('view.charts.mutual_friend.force_dialog.title') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<template #footer>
|
||||
<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>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<p class="mutual-graph__force-description">
|
||||
{{ 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>
|
||||
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
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 { NumberField, NumberFieldContent, NumberFieldInput } from '@/components/ui/number-field';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
@@ -1,64 +1,71 @@
|
||||
<template>
|
||||
<el-dialog v-model="isDialogVisible" :title="t('dialog.avatar_export.header')" width="650px">
|
||||
<div style="margin-bottom: 10px" class="flex flex-col gap-2">
|
||||
<label v-for="option in exportSelectOptions" :key="option.value" class="inline-flex items-center gap-2">
|
||||
<Checkbox
|
||||
:model-value="exportSelectedOptions.includes(option.label)"
|
||||
@update:modelValue="(val) => toggleAvatarExportOption(option.label, val)" />
|
||||
<span>{{ option.label }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<Dialog v-model:open="isDialogVisible">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.avatar_export.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<Select
|
||||
:model-value="avatarExportFavoriteGroupSelection"
|
||||
@update:modelValue="handleAvatarExportFavoriteGroupSelect">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue placeholder="All Favorites" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<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>
|
||||
<div style="margin-bottom: 10px" class="flex flex-col gap-2">
|
||||
<label v-for="option in exportSelectOptions" :key="option.value" class="inline-flex items-center gap-2">
|
||||
<Checkbox
|
||||
:model-value="exportSelectedOptions.includes(option.label)"
|
||||
@update:modelValue="(val) => toggleAvatarExportOption(option.label, val)" />
|
||||
<span>{{ option.label }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<Select
|
||||
:model-value="avatarExportLocalFavoriteGroupSelection"
|
||||
@update:modelValue="handleAvatarExportLocalFavoriteGroupSelect"
|
||||
style="margin-left: 10px">
|
||||
<SelectTrigger size="sm">
|
||||
<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" />
|
||||
</el-dialog>
|
||||
<div class="flex items-center gap-2">
|
||||
<Select
|
||||
:model-value="avatarExportFavoriteGroupSelection"
|
||||
@update:modelValue="handleAvatarExportFavoriteGroupSelect">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue placeholder="All Favorites" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<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
|
||||
:model-value="avatarExportLocalFavoriteGroupSelection"
|
||||
@update:modelValue="handleAvatarExportLocalFavoriteGroupSelect"
|
||||
style="margin-left: 10px">
|
||||
<SelectTrigger size="sm">
|
||||
<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>
|
||||
|
||||
<script setup>
|
||||
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 { Checkbox } from '@/components/ui/checkbox';
|
||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
|
||||
@@ -1,120 +1,122 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:z-index="avatarImportDialogIndex"
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.avatar_import.header')"
|
||||
width="650px">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<div style="font-size: 12px">{{ t('dialog.avatar_import.description') }}</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<div v-if="avatarImportDialog.progress">
|
||||
{{ t('dialog.avatar_import.process_progress') }} {{ avatarImportDialog.progress }} /
|
||||
{{ avatarImportDialog.progressTotal }}
|
||||
<Loader2 style="margin: 0 5px" />
|
||||
<Dialog v-model:open="isVisible">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.avatar_import.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<div style="font-size: 12px">{{ t('dialog.avatar_import.description') }}</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<div v-if="avatarImportDialog.progress">
|
||||
{{ t('dialog.avatar_import.process_progress') }} {{ avatarImportDialog.progress }} /
|
||||
{{ avatarImportDialog.progressTotal }}
|
||||
<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>
|
||||
<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>
|
||||
<InputGroupTextareaField
|
||||
v-model="avatarImportDialog.input"
|
||||
:rows="10"
|
||||
style="margin-top: 10px"
|
||||
input-class="resize-none" />
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 5px">
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Select
|
||||
:model-value="avatarImportFavoriteGroupSelection"
|
||||
@update:modelValue="handleAvatarImportGroupSelect"
|
||||
style="margin-right: 5px">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue :placeholder="t('dialog.avatar_import.select_group_placeholder')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="groupAPI in favoriteAvatarGroups"
|
||||
:key="groupAPI.name"
|
||||
:value="groupAPI.name"
|
||||
:disabled="groupAPI.count >= groupAPI.capacity">
|
||||
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<InputGroupTextareaField
|
||||
v-model="avatarImportDialog.input"
|
||||
:rows="10"
|
||||
style="margin-top: 10px"
|
||||
input-class="resize-none" />
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 5px">
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Select
|
||||
:model-value="avatarImportFavoriteGroupSelection"
|
||||
@update:modelValue="handleAvatarImportGroupSelect"
|
||||
style="margin-right: 5px">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue :placeholder="t('dialog.avatar_import.select_group_placeholder')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="groupAPI in favoriteAvatarGroups"
|
||||
:key="groupAPI.name"
|
||||
:value="groupAPI.name"
|
||||
:disabled="groupAPI.count >= groupAPI.capacity">
|
||||
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select
|
||||
:model-value="avatarImportLocalFavoriteGroupSelection"
|
||||
@update:modelValue="handleAvatarImportLocalGroupSelect"
|
||||
style="margin-left: 10px">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue :placeholder="t('dialog.avatar_import.select_group_placeholder')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem v-for="group in localAvatarFavoriteGroups" :key="group" :value="group">
|
||||
{{ group }} ({{ localAvatarFavGroupLength(group) }})
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select
|
||||
:model-value="avatarImportLocalFavoriteGroupSelection"
|
||||
@update:modelValue="handleAvatarImportLocalGroupSelect"
|
||||
style="margin-left: 10px">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue :placeholder="t('dialog.avatar_import.select_group_placeholder')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem v-for="group in localAvatarFavoriteGroups" :key="group" :value="group">
|
||||
{{ group }} ({{ localAvatarFavGroupLength(group) }})
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</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>
|
||||
<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') }}
|
||||
<span v-if="avatarImportDialog.importProgress" style="margin: 10px">
|
||||
<Loader2 style="margin-right: 5px" />
|
||||
{{ 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
|
||||
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.importProgress" style="margin: 10px">
|
||||
<Loader2 style="margin-right: 5px" />
|
||||
{{ 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>
|
||||
<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" />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
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 { Button } from '@/components/ui/button';
|
||||
import { DataTableLayout } from '@/components/ui/data-table';
|
||||
@@ -127,7 +129,6 @@
|
||||
import { useAvatarStore, useFavoriteStore, useGalleryStore, useUserStore } from '../../../stores';
|
||||
import { avatarRequest, favoriteRequest } from '../../../api';
|
||||
import { createColumns } from './avatarImportColumns.jsx';
|
||||
import { getNextDialogIndex } from '../../../shared/utils/base/ui';
|
||||
import { removeFromArray } from '../../../shared/utils';
|
||||
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
|
||||
|
||||
@@ -189,8 +190,6 @@
|
||||
enableSorting: false
|
||||
});
|
||||
|
||||
const avatarImportDialogIndex = ref(2000);
|
||||
|
||||
const isVisible = computed({
|
||||
get() {
|
||||
return avatarImportDialogVisible.value;
|
||||
@@ -204,7 +203,6 @@
|
||||
() => avatarImportDialogVisible.value,
|
||||
(value) => {
|
||||
if (value) {
|
||||
avatarImportDialogIndex.value = getNextDialogIndex();
|
||||
clearAvatarImportTable();
|
||||
resetAvatarImport();
|
||||
if (avatarImportDialogInput.value) {
|
||||
|
||||
@@ -1,41 +1,47 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="isDialogVisible"
|
||||
class="x-dialog"
|
||||
:title="t('dialog.friend_export.header')"
|
||||
width="650px"
|
||||
destroy-on-close>
|
||||
<Select :model-value="friendExportFavoriteGroupSelection" @update:modelValue="handleFriendExportGroupSelect">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue placeholder="All Favorites" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem :value="FRIEND_EXPORT_ALL_VALUE">All Favorites</SelectItem>
|
||||
<SelectItem v-for="groupAPI in favoriteFriendGroups" :key="groupAPI.name" :value="groupAPI.name">
|
||||
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Dialog v-model:open="isDialogVisible">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.friend_export.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Select
|
||||
:model-value="friendExportFavoriteGroupSelection"
|
||||
@update:modelValue="handleFriendExportGroupSelect">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue placeholder="All Favorites" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem :value="FRIEND_EXPORT_ALL_VALUE">All Favorites</SelectItem>
|
||||
<SelectItem
|
||||
v-for="groupAPI in favoriteFriendGroups"
|
||||
:key="groupAPI.name"
|
||||
:value="groupAPI.name">
|
||||
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<InputGroupTextareaField
|
||||
v-model="friendExportContent"
|
||||
:rows="15"
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
input-class="resize-none"
|
||||
@click="handleCopyFriendExportData" />
|
||||
</el-dialog>
|
||||
<InputGroupTextareaField
|
||||
v-model="friendExportContent"
|
||||
:rows="15"
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
input-class="resize-none"
|
||||
@click="handleCopyFriendExportData" />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
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 { storeToRefs } from 'pinia';
|
||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
|
||||
@@ -1,101 +1,103 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:z-index="friendImportDialogIndex"
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.friend_import.header')"
|
||||
width="650px">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<div style="font-size: 12px">{{ t('dialog.friend_import.description') }}</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<div v-if="friendImportDialog.progress">
|
||||
{{ t('dialog.friend_import.process_progress') }} {{ friendImportDialog.progress }} /
|
||||
{{ friendImportDialog.progressTotal }}
|
||||
<Loader2 style="margin: 0 5px" />
|
||||
<Dialog v-model:open="isVisible">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.friend_import.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<div style="font-size: 12px">{{ t('dialog.friend_import.description') }}</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<div v-if="friendImportDialog.progress">
|
||||
{{ t('dialog.friend_import.process_progress') }} {{ friendImportDialog.progress }} /
|
||||
{{ friendImportDialog.progressTotal }}
|
||||
<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>
|
||||
<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>
|
||||
<InputGroupTextareaField
|
||||
v-model="friendImportDialog.input"
|
||||
:rows="10"
|
||||
style="margin-top: 10px"
|
||||
input-class="resize-none" />
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 5px">
|
||||
<div>
|
||||
<Select
|
||||
:model-value="friendImportFavoriteGroupSelection"
|
||||
@update:modelValue="handleFriendImportGroupSelect">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue :placeholder="t('dialog.friend_import.select_group_placeholder')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="groupAPI in favoriteFriendGroups"
|
||||
:key="groupAPI.name"
|
||||
:value="groupAPI.name"
|
||||
:disabled="groupAPI.count >= groupAPI.capacity">
|
||||
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<span v-if="friendImportDialog.friendImportFavoriteGroup" style="margin-left: 5px">
|
||||
{{ friendImportTable.data.length }} /
|
||||
{{
|
||||
friendImportDialog.friendImportFavoriteGroup.capacity -
|
||||
friendImportDialog.friendImportFavoriteGroup.count
|
||||
}}
|
||||
</span>
|
||||
<InputGroupTextareaField
|
||||
v-model="friendImportDialog.input"
|
||||
:rows="10"
|
||||
style="margin-top: 10px"
|
||||
input-class="resize-none" />
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 5px">
|
||||
<div>
|
||||
<Select
|
||||
:model-value="friendImportFavoriteGroupSelection"
|
||||
@update:modelValue="handleFriendImportGroupSelect">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue :placeholder="t('dialog.friend_import.select_group_placeholder')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="groupAPI in favoriteFriendGroups"
|
||||
:key="groupAPI.name"
|
||||
:value="groupAPI.name"
|
||||
:disabled="groupAPI.count >= groupAPI.capacity">
|
||||
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<span v-if="friendImportDialog.friendImportFavoriteGroup" style="margin-left: 5px">
|
||||
{{ friendImportTable.data.length }} /
|
||||
{{
|
||||
friendImportDialog.friendImportFavoriteGroup.capacity -
|
||||
friendImportDialog.friendImportFavoriteGroup.count
|
||||
}}
|
||||
</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>
|
||||
<Button
|
||||
size="sm"
|
||||
class="mr-2"
|
||||
variant="secondary"
|
||||
:disabled="friendImportTable.data.length === 0"
|
||||
@click="clearFriendImportTable">
|
||||
{{ t('dialog.friend_import.clear_table') }}
|
||||
<span v-if="friendImportDialog.importProgress" style="margin: 10px">
|
||||
<Loader2 style="margin-right: 5px" />
|
||||
{{ t('dialog.friend_import.import_progress') }} {{ friendImportDialog.importProgress }}/{{
|
||||
friendImportDialog.importProgressTotal
|
||||
}}
|
||||
</span>
|
||||
<br />
|
||||
<template v-if="friendImportDialog.errors">
|
||||
<Button size="sm" variant="secondary" @click="friendImportDialog.errors = ''">
|
||||
{{ t('dialog.friend_import.clear_errors') }}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
:disabled="friendImportTable.data.length === 0 || !friendImportDialog.friendImportFavoriteGroup"
|
||||
@click="importFriendImportTable">
|
||||
{{ t('dialog.friend_import.import') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<span v-if="friendImportDialog.importProgress" style="margin: 10px">
|
||||
<Loader2 style="margin-right: 5px" />
|
||||
{{ t('dialog.friend_import.import_progress') }} {{ friendImportDialog.importProgress }}/{{
|
||||
friendImportDialog.importProgressTotal
|
||||
}}
|
||||
</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>
|
||||
<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" />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
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 { Button } from '@/components/ui/button';
|
||||
import { DataTableLayout } from '@/components/ui/data-table';
|
||||
@@ -109,7 +111,6 @@
|
||||
import { useFavoriteStore, useGalleryStore, useUserStore } from '../../../stores';
|
||||
import { favoriteRequest, userRequest } from '../../../api';
|
||||
import { createColumns } from './friendImportColumns.jsx';
|
||||
import { getNextDialogIndex } from '../../../shared/utils/base/ui';
|
||||
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -169,8 +170,6 @@
|
||||
enableSorting: false
|
||||
});
|
||||
|
||||
const friendImportDialogIndex = ref(2000);
|
||||
|
||||
const isVisible = computed({
|
||||
get() {
|
||||
return friendImportDialogVisible.value;
|
||||
@@ -184,7 +183,6 @@
|
||||
() => friendImportDialogVisible.value,
|
||||
(value) => {
|
||||
if (value) {
|
||||
friendImportDialogIndex.value = getNextDialogIndex();
|
||||
clearFriendImportTable();
|
||||
resetFriendImport();
|
||||
friendImportFavoriteGroupSelection.value =
|
||||
|
||||
@@ -1,61 +1,73 @@
|
||||
<template>
|
||||
<el-dialog v-model="isDialogVisible" :title="t('dialog.world_export.header')" width="650px">
|
||||
<div style="margin-bottom: 10px" class="flex flex-col gap-2">
|
||||
<label v-for="option in exportSelectOptions" :key="option.value" class="inline-flex items-center gap-2">
|
||||
<Checkbox
|
||||
:model-value="exportSelectedOptions.includes(option.label)"
|
||||
@update:modelValue="(val) => toggleWorldExportOption(option.label, val)" />
|
||||
<span>{{ option.label }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<Dialog v-model:open="isDialogVisible">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.world_export.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<Select :model-value="worldExportFavoriteGroupSelection" @update:modelValue="handleWorldExportGroupSelect">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue placeholder="All Favorites" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem :value="WORLD_EXPORT_ALL_VALUE">None</SelectItem>
|
||||
<SelectItem v-for="groupAPI in favoriteWorldGroups" :key="groupAPI.name" :value="groupAPI.name">
|
||||
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div style="margin-bottom: 10px" class="flex flex-col gap-2">
|
||||
<label v-for="option in exportSelectOptions" :key="option.value" class="inline-flex items-center gap-2">
|
||||
<Checkbox
|
||||
:model-value="exportSelectedOptions.includes(option.label)"
|
||||
@update:modelValue="(val) => toggleWorldExportOption(option.label, val)" />
|
||||
<span>{{ option.label }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<div class="flex items-center gap-2">
|
||||
<Select
|
||||
:model-value="worldExportFavoriteGroupSelection"
|
||||
@update:modelValue="handleWorldExportGroupSelect">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue placeholder="All Favorites" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem :value="WORLD_EXPORT_ALL_VALUE">None</SelectItem>
|
||||
<SelectItem
|
||||
v-for="groupAPI in favoriteWorldGroups"
|
||||
:key="groupAPI.name"
|
||||
:value="groupAPI.name">
|
||||
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
|
||||
</SelectItem>
|
||||
</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
|
||||
v-model="worldExportContent"
|
||||
:rows="15"
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
input-class="resize-none"
|
||||
@click="handleCopyWorldExportData" />
|
||||
</el-dialog>
|
||||
<br />
|
||||
|
||||
<InputGroupTextareaField
|
||||
v-model="worldExportContent"
|
||||
:rows="15"
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
input-class="resize-none"
|
||||
@click="handleCopyWorldExportData" />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
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 { Checkbox } from '@/components/ui/checkbox';
|
||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
|
||||
@@ -1,125 +1,126 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:z-index="worldImportDialogIndex"
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.world_import.header')"
|
||||
width="650px"
|
||||
class="x-dialog">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<div style="font-size: 12px">{{ t('dialog.world_import.description') }}</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<div v-if="worldImportDialog.progress">
|
||||
{{ t('dialog.world_import.process_progress') }}
|
||||
{{ worldImportDialog.progress }} / {{ worldImportDialog.progressTotal }}
|
||||
<Loader2 style="margin: 0 5px" />
|
||||
<Dialog v-model:open="isVisible">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.world_import.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<div style="font-size: 12px">{{ t('dialog.world_import.description') }}</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<div v-if="worldImportDialog.progress">
|
||||
{{ t('dialog.world_import.process_progress') }}
|
||||
{{ worldImportDialog.progress }} / {{ worldImportDialog.progressTotal }}
|
||||
<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>
|
||||
<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>
|
||||
<InputGroupTextareaField
|
||||
v-model="worldImportDialog.input"
|
||||
:rows="10"
|
||||
style="margin-top: 10px"
|
||||
input-class="resize-none" />
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 5px">
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Select
|
||||
:model-value="worldImportFavoriteGroupSelection"
|
||||
@update:modelValue="handleWorldImportGroupSelect">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue :placeholder="t('dialog.world_import.select_vrchat_group_placeholder')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="groupAPI in favoriteWorldGroups"
|
||||
:key="groupAPI.name"
|
||||
:value="groupAPI.name"
|
||||
:disabled="groupAPI.count >= groupAPI.capacity">
|
||||
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<InputGroupTextareaField
|
||||
v-model="worldImportDialog.input"
|
||||
:rows="10"
|
||||
style="margin-top: 10px"
|
||||
input-class="resize-none" />
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 5px">
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Select
|
||||
:model-value="worldImportFavoriteGroupSelection"
|
||||
@update:modelValue="handleWorldImportGroupSelect">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue :placeholder="t('dialog.world_import.select_vrchat_group_placeholder')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="groupAPI in favoriteWorldGroups"
|
||||
:key="groupAPI.name"
|
||||
:value="groupAPI.name"
|
||||
:disabled="groupAPI.count >= groupAPI.capacity">
|
||||
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select
|
||||
:model-value="worldImportLocalFavoriteGroupSelection"
|
||||
@update:modelValue="handleWorldImportLocalGroupSelect"
|
||||
style="margin-left: 10px">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue :placeholder="t('dialog.world_import.select_local_group_placeholder')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem v-for="group in localWorldFavoriteGroups" :key="group" :value="group">
|
||||
{{ group }} ({{ localWorldFavGroupLength(group) }})
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select
|
||||
:model-value="worldImportLocalFavoriteGroupSelection"
|
||||
@update:modelValue="handleWorldImportLocalGroupSelect"
|
||||
style="margin-left: 10px">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue :placeholder="t('dialog.world_import.select_local_group_placeholder')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem v-for="group in localWorldFavoriteGroups" :key="group" :value="group">
|
||||
{{ group }} ({{ localWorldFavGroupLength(group) }})
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</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>
|
||||
<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') }}
|
||||
<span v-if="worldImportDialog.importProgress" style="margin: 10px">
|
||||
<Loader2 style="margin-right: 5px" />
|
||||
{{ 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>
|
||||
<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.importProgress" style="margin: 10px">
|
||||
<Loader2 style="margin-right: 5px" />
|
||||
{{ 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>
|
||||
<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" />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
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 { Button } from '@/components/ui/button';
|
||||
import { DataTableLayout } from '@/components/ui/data-table';
|
||||
@@ -132,7 +133,6 @@
|
||||
import { useFavoriteStore, useGalleryStore, useUserStore, useWorldStore } from '../../../stores';
|
||||
import { favoriteRequest, worldRequest } from '../../../api';
|
||||
import { createColumns } from './worldImportColumns.jsx';
|
||||
import { getNextDialogIndex } from '../../../shared/utils/base/ui';
|
||||
import { removeFromArray } from '../../../shared/utils';
|
||||
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
|
||||
|
||||
@@ -147,8 +147,6 @@
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const worldImportDialogIndex = ref(2000);
|
||||
|
||||
const worldImportDialog = ref({
|
||||
loading: false,
|
||||
progress: 0,
|
||||
@@ -211,7 +209,6 @@
|
||||
() => worldImportDialogVisible.value,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
worldImportDialogIndex.value = getNextDialogIndex();
|
||||
clearWorldImportTable();
|
||||
resetWorldImport();
|
||||
if (worldImportDialogInput.value) {
|
||||
|
||||
@@ -78,34 +78,37 @@
|
||||
</div>
|
||||
</template>
|
||||
</DataTableLayout>
|
||||
<el-dialog
|
||||
v-model="friendsListLoadDialogVisible"
|
||||
:title="t('view.friend_list.load_dialog_title')"
|
||||
width="420px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:show-close="false"
|
||||
align-center>
|
||||
<div style="margin-bottom: 10px" v-text="t('view.friend_list.load_dialog_message')"></div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Progress :model-value="friendsListLoadingPercent" class="h-4 w-full" />
|
||||
<span class="text-xs w-10 text-right">{{ friendsListLoadingPercent }}%</span>
|
||||
</div>
|
||||
<div style="margin-top: 10px; text-align: right">
|
||||
<span>{{ friendsListLoadingCurrent }} / {{ friendsListLoadingTotal }}</span>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button variant="secondary" @click="cancelFriendsListLoad">
|
||||
{{ t('view.friend_list.load_cancel') }}
|
||||
</Button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<Dialog v-model:open="friendsListLoadDialogVisible">
|
||||
<DialogContent
|
||||
:show-close-button="false"
|
||||
@interact-outside.prevent
|
||||
@escape-key-down.prevent
|
||||
class="sm:max-w-[420px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('view.friend_list.load_dialog_title') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div style="margin-bottom: 10px" v-text="t('view.friend_list.load_dialog_message')"></div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Progress :model-value="friendsListLoadingPercent" class="h-4 w-full" />
|
||||
<span class="text-xs w-10 text-right">{{ friendsListLoadingPercent }}%</span>
|
||||
</div>
|
||||
<div style="margin-top: 10px; text-align: right">
|
||||
<span>{{ friendsListLoadingCurrent }} / {{ friendsListLoadingTotal }}</span>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" @click="cancelFriendsListLoad">
|
||||
{{ t('view.friend_list.load_cancel') }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
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 { Button } from '@/components/ui/button';
|
||||
import { InputGroupField } from '@/components/ui/input-group';
|
||||
|
||||
@@ -1,33 +1,37 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="editAndSendInviteResponseDialog.visible"
|
||||
@close="cancelEditAndSendInviteResponse"
|
||||
:title="t('dialog.edit_send_invite_response_message.header')"
|
||||
width="400px"
|
||||
append-to-body>
|
||||
<div style="font-size: 12px">
|
||||
<span>{{ t('dialog.edit_send_invite_response_message.description') }}</span>
|
||||
</div>
|
||||
<InputGroupTextareaField
|
||||
v-model="editAndSendInviteResponseDialog.newMessage"
|
||||
:maxlength="64"
|
||||
:rows="2"
|
||||
class="mt-2.5"
|
||||
placeholder=""
|
||||
show-count />
|
||||
<template #footer>
|
||||
<Button variant="secondary" class="mr-2" @click="cancelEditAndSendInviteResponse">{{
|
||||
t('dialog.edit_send_invite_response_message.cancel')
|
||||
}}</Button>
|
||||
<Button @click="saveEditAndSendInviteResponse" :disabled="!editAndSendInviteResponseDialog.newMessage">{{
|
||||
t('dialog.edit_send_invite_response_message.send')
|
||||
}}</Button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<Dialog
|
||||
:open="editAndSendInviteResponseDialog.visible"
|
||||
@update:open="(open) => (open ? null : cancelEditAndSendInviteResponse())">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.edit_send_invite_response_message.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div style="font-size: 12px">
|
||||
<span>{{ t('dialog.edit_send_invite_response_message.description') }}</span>
|
||||
</div>
|
||||
<InputGroupTextareaField
|
||||
v-model="editAndSendInviteResponseDialog.newMessage"
|
||||
:maxlength="64"
|
||||
:rows="2"
|
||||
class="mt-2.5"
|
||||
placeholder=""
|
||||
show-count />
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" class="mr-2" @click="cancelEditAndSendInviteResponse">{{
|
||||
t('dialog.edit_send_invite_response_message.cancel')
|
||||
}}</Button>
|
||||
<Button
|
||||
@click="saveEditAndSendInviteResponse"
|
||||
:disabled="!editAndSendInviteResponseDialog.newMessage"
|
||||
>{{ t('dialog.edit_send_invite_response_message.send') }}</Button
|
||||
>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
@@ -1,44 +1,46 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="sendInviteRequestResponseDialogVisible"
|
||||
:title="t('dialog.invite_request_response_message.header')"
|
||||
width="800px"
|
||||
append-to-body
|
||||
@close="cancelSendInviteRequestResponse">
|
||||
<template v-if="isLocalUserVrcPlusSupporter">
|
||||
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
|
||||
</template>
|
||||
<Dialog
|
||||
:open="sendInviteRequestResponseDialogVisible"
|
||||
@update:open="(open) => (open ? null : cancelSendInviteRequestResponse())">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.invite_request_response_message.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<template v-if="isLocalUserVrcPlusSupporter">
|
||||
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
|
||||
</template>
|
||||
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px"
|
||||
:table="inviteRequestResponseTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleInviteRequestResponseRowClick" />
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px"
|
||||
:table="inviteRequestResponseTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleInviteRequestResponseRowClick" />
|
||||
|
||||
<template #footer>
|
||||
<Button variant="secondary" class="mr-2" @click="cancelSendInviteRequestResponse">
|
||||
{{ t('dialog.invite_request_response_message.cancel') }}
|
||||
</Button>
|
||||
<Button @click="refreshInviteMessageTableData('requestResponse')">
|
||||
{{ t('dialog.invite_request_response_message.refresh') }}
|
||||
</Button>
|
||||
</template>
|
||||
<EditAndSendInviteResponseDialog
|
||||
:edit-and-send-invite-response-dialog="editAndSendInviteResponseDialog"
|
||||
:send-invite-response-dialog="sendInviteResponseDialog"
|
||||
@closeInviteDialog="closeInviteDialog"
|
||||
@closeResponseConfirmDialog="closeResponseConfirmDialog" />
|
||||
<SendInviteResponseConfirmDialog
|
||||
:send-invite-response-dialog="sendInviteResponseDialog"
|
||||
:send-invite-response-confirm-dialog="sendInviteResponseConfirmDialog"
|
||||
@closeInviteDialog="closeInviteDialog"
|
||||
@closeResponseConfirmDialog="closeResponseConfirmDialog" />
|
||||
</el-dialog>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" class="mr-2" @click="cancelSendInviteRequestResponse">
|
||||
{{ t('dialog.invite_request_response_message.cancel') }}
|
||||
</Button>
|
||||
<Button @click="refreshInviteMessageTableData('requestResponse')">
|
||||
{{ t('dialog.invite_request_response_message.refresh') }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
<EditAndSendInviteResponseDialog
|
||||
:edit-and-send-invite-response-dialog="editAndSendInviteResponseDialog"
|
||||
:send-invite-response-dialog="sendInviteResponseDialog"
|
||||
@closeInviteDialog="closeInviteDialog"
|
||||
@closeResponseConfirmDialog="closeResponseConfirmDialog" />
|
||||
<SendInviteResponseConfirmDialog
|
||||
:send-invite-response-dialog="sendInviteResponseDialog"
|
||||
:send-invite-response-confirm-dialog="sendInviteResponseConfirmDialog"
|
||||
@closeInviteDialog="closeInviteDialog"
|
||||
@closeResponseConfirmDialog="closeResponseConfirmDialog" />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, ref } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DataTableLayout } from '@/components/ui/data-table';
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="sendInviteResponseConfirmDialog.visible"
|
||||
:title="t('dialog.invite_response_message.header')"
|
||||
width="400px"
|
||||
append-to-body
|
||||
@close="cancelInviteResponseConfirm">
|
||||
<div style="font-size: 12px">
|
||||
<span>{{ t('dialog.invite_response_message.confirmation') }}</span>
|
||||
</div>
|
||||
<Dialog
|
||||
:open="sendInviteResponseConfirmDialog.visible"
|
||||
@update:open="(open) => (open ? null : cancelInviteResponseConfirm())">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.invite_response_message.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div style="font-size: 12px">
|
||||
<span>{{ t('dialog.invite_response_message.confirmation') }}</span>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<Button variant="secondary" class="mr-2" @click="cancelInviteResponseConfirm">{{
|
||||
t('dialog.invite_response_message.cancel')
|
||||
}}</Button>
|
||||
<Button @click="sendInviteResponseConfirm">{{ t('dialog.invite_response_message.confirm') }}</Button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" class="mr-2" @click="cancelInviteResponseConfirm">{{
|
||||
t('dialog.invite_response_message.cancel')
|
||||
}}</Button>
|
||||
<Button @click="sendInviteResponseConfirm">{{ t('dialog.invite_response_message.confirm') }}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="sendInviteResponseDialogVisible"
|
||||
:title="t('dialog.invite_response_message.header')"
|
||||
width="800px"
|
||||
append-to-body
|
||||
@close="cancelSendInviteResponse">
|
||||
<template v-if="isLocalUserVrcPlusSupporter">
|
||||
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
|
||||
</template>
|
||||
<Dialog :open="sendInviteResponseDialogVisible" @update:open="(open) => (open ? null : cancelSendInviteResponse())">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.invite_response_message.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<template v-if="isLocalUserVrcPlusSupporter">
|
||||
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
|
||||
</template>
|
||||
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px"
|
||||
:table="inviteResponseTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleInviteResponseRowClick" />
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px"
|
||||
:table="inviteResponseTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleInviteResponseRowClick" />
|
||||
|
||||
<template #footer>
|
||||
<Button variant="secondary" class="mr-2" @click="cancelSendInviteResponse">{{
|
||||
t('dialog.invite_response_message.cancel')
|
||||
}}</Button>
|
||||
<Button @click="refreshInviteMessageTableData('response')">{{
|
||||
t('dialog.invite_response_message.refresh')
|
||||
}}</Button>
|
||||
</template>
|
||||
<EditAndSendInviteResponseDialog
|
||||
:edit-and-send-invite-response-dialog="editAndSendInviteResponseDialog"
|
||||
:send-invite-response-dialog="sendInviteResponseDialog"
|
||||
@closeResponseConfirmDialog="closeResponseConfirmDialog"
|
||||
@closeInviteDialog="closeInviteDialog" />
|
||||
<SendInviteResponseConfirmDialog
|
||||
:send-invite-response-dialog="sendInviteResponseDialog"
|
||||
:send-invite-response-confirm-dialog="sendInviteResponseConfirmDialog"
|
||||
@closeResponseConfirmDialog="closeResponseConfirmDialog"
|
||||
@closeInviteDialog="closeInviteDialog" />
|
||||
</el-dialog>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" class="mr-2" @click="cancelSendInviteResponse">{{
|
||||
t('dialog.invite_response_message.cancel')
|
||||
}}</Button>
|
||||
<Button @click="refreshInviteMessageTableData('response')">{{
|
||||
t('dialog.invite_response_message.refresh')
|
||||
}}</Button>
|
||||
</DialogFooter>
|
||||
<EditAndSendInviteResponseDialog
|
||||
:edit-and-send-invite-response-dialog="editAndSendInviteResponseDialog"
|
||||
:send-invite-response-dialog="sendInviteResponseDialog"
|
||||
@closeResponseConfirmDialog="closeResponseConfirmDialog"
|
||||
@closeInviteDialog="closeInviteDialog" />
|
||||
<SendInviteResponseConfirmDialog
|
||||
:send-invite-response-dialog="sendInviteResponseDialog"
|
||||
:send-invite-response-confirm-dialog="sendInviteResponseConfirmDialog"
|
||||
@closeResponseConfirmDialog="closeResponseConfirmDialog"
|
||||
@closeInviteDialog="closeInviteDialog" />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, ref } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DataTableLayout } from '@/components/ui/data-table';
|
||||
|
||||
@@ -1,60 +1,62 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
v-model="chatboxBlacklistDialog.visible"
|
||||
:title="t('dialog.chatbox_blacklist.header')"
|
||||
width="600px">
|
||||
<div v-if="chatboxBlacklistDialog.visible" v-loading="chatboxBlacklistDialog.loading">
|
||||
<h2>{{ t('dialog.chatbox_blacklist.keyword_blacklist') }}</h2>
|
||||
<InputGroupAction
|
||||
v-for="(item, index) in chatboxBlacklist"
|
||||
:key="index"
|
||||
v-model="chatboxBlacklist[index]"
|
||||
size="sm"
|
||||
style="margin-top: 5px"
|
||||
@change="saveChatboxBlacklist">
|
||||
<template #actions>
|
||||
<Button
|
||||
variant="outline"
|
||||
@click="
|
||||
chatboxBlacklist.splice(index, 1);
|
||||
saveChatboxBlacklist();
|
||||
">
|
||||
</Button>
|
||||
</template>
|
||||
</InputGroupAction>
|
||||
<Button size="sm" variant="outline" style="margin-top: 5px" @click="chatboxBlacklist.push('')">
|
||||
{{ t('dialog.chatbox_blacklist.add_item') }}
|
||||
</Button>
|
||||
<br />
|
||||
<h2>{{ t('dialog.chatbox_blacklist.user_blacklist') }}</h2>
|
||||
<Badge
|
||||
v-for="user in chatboxUserBlacklist"
|
||||
:key="user[0]"
|
||||
variant="outline"
|
||||
style="margin-right: 5px; margin-top: 5px">
|
||||
<span>{{ user[1] }}</span>
|
||||
<button
|
||||
type="button"
|
||||
style="
|
||||
margin-left: 6px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
"
|
||||
@click="deleteChatboxUserBlacklist(user[0])">
|
||||
<X class="h-3 w-3" style="line-height: 1" />
|
||||
</button>
|
||||
</Badge>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<Dialog v-model:open="chatboxBlacklistDialog.visible">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.chatbox_blacklist.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div v-if="chatboxBlacklistDialog.visible" v-loading="chatboxBlacklistDialog.loading">
|
||||
<h2>{{ t('dialog.chatbox_blacklist.keyword_blacklist') }}</h2>
|
||||
<InputGroupAction
|
||||
v-for="(item, index) in chatboxBlacklist"
|
||||
:key="index"
|
||||
v-model="chatboxBlacklist[index]"
|
||||
size="sm"
|
||||
style="margin-top: 5px"
|
||||
@change="saveChatboxBlacklist">
|
||||
<template #actions>
|
||||
<Button
|
||||
variant="outline"
|
||||
@click="
|
||||
chatboxBlacklist.splice(index, 1);
|
||||
saveChatboxBlacklist();
|
||||
">
|
||||
</Button>
|
||||
</template>
|
||||
</InputGroupAction>
|
||||
<Button size="sm" variant="outline" style="margin-top: 5px" @click="chatboxBlacklist.push('')">
|
||||
{{ t('dialog.chatbox_blacklist.add_item') }}
|
||||
</Button>
|
||||
<br />
|
||||
<h2>{{ t('dialog.chatbox_blacklist.user_blacklist') }}</h2>
|
||||
<Badge
|
||||
v-for="user in chatboxUserBlacklist"
|
||||
:key="user[0]"
|
||||
variant="outline"
|
||||
style="margin-right: 5px; margin-top: 5px">
|
||||
<span>{{ user[1] }}</span>
|
||||
<button
|
||||
type="button"
|
||||
style="
|
||||
margin-left: 6px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
"
|
||||
@click="deleteChatboxUserBlacklist(user[0])">
|
||||
<X class="h-3 w-3" style="line-height: 1" />
|
||||
</button>
|
||||
</Badge>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InputGroupAction } from '@/components/ui/input-group';
|
||||
import { X } from 'lucide-vue-next';
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="isAvatarProviderDialogVisible"
|
||||
:title="t('dialog.avatar_database_provider.header')"
|
||||
width="600px"
|
||||
@close="closeDialog">
|
||||
<div>
|
||||
<InputGroupAction
|
||||
v-for="(provider, index) in avatarRemoteDatabaseProviderList"
|
||||
:key="index"
|
||||
v-model="avatarRemoteDatabaseProviderList[index]"
|
||||
size="sm"
|
||||
style="margin-top: 5px"
|
||||
@change="saveAvatarProviderList">
|
||||
<template #actions>
|
||||
<Button variant="outline" size="icon" @click="removeAvatarProvider(provider)">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</template>
|
||||
</InputGroupAction>
|
||||
<Dialog :open="isAvatarProviderDialogVisible" @update:open="(open) => (open ? null : closeDialog())">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.avatar_database_provider.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div>
|
||||
<InputGroupAction
|
||||
v-for="(provider, index) in avatarRemoteDatabaseProviderList"
|
||||
:key="index"
|
||||
v-model="avatarRemoteDatabaseProviderList[index]"
|
||||
size="sm"
|
||||
style="margin-top: 5px"
|
||||
@change="saveAvatarProviderList">
|
||||
<template #actions>
|
||||
<Button variant="outline" size="icon" @click="removeAvatarProvider(provider)">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</template>
|
||||
</InputGroupAction>
|
||||
|
||||
<Button size="sm" style="margin-top: 5px" @click="avatarRemoteDatabaseProviderList.push('')">
|
||||
{{ t('dialog.avatar_database_provider.add_provider') }}
|
||||
</Button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<Button size="sm" style="margin-top: 5px" @click="avatarRemoteDatabaseProviderList.push('')">
|
||||
{{ t('dialog.avatar_database_provider.add_provider') }}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InputGroupAction } from '@/components/ui/input-group';
|
||||
import { Trash2 } from 'lucide-vue-next';
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="changeLogDialog.visible"
|
||||
:title="t('dialog.change_log.header')"
|
||||
width="800px"
|
||||
append-to-body
|
||||
@close="closeDialog">
|
||||
<div v-loading="!changeLogDialog.changeLog" class="changelog-dialog">
|
||||
<h2 v-text="changeLogDialog.buildName"></h2>
|
||||
<span v-show="changeLogDialog.buildName">
|
||||
{{ t('dialog.change_log.description') }}
|
||||
<a class="x-link" @click="openExternalLink('https://www.patreon.com/Natsumi_VRCX')">Patreon</a>,
|
||||
<a class="x-link" @click="openExternalLink('https://ko-fi.com/natsumi_sama')">Ko-fi</a>.
|
||||
</span>
|
||||
<VueShowdown
|
||||
:markdown="changeLogDialog.changeLog"
|
||||
flavor="github"
|
||||
:options="showdownOptions"
|
||||
@click="handleLinkClick"
|
||||
style="height: 62vh; overflow-y: auto; margin-top: 10px" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="mr-2"
|
||||
@click="openExternalLink('https://github.com/vrcx-team/VRCX/releases')">
|
||||
{{ t('dialog.change_log.github') }}
|
||||
</Button>
|
||||
<Button variant="outline" class="mr-2" @click="openExternalLink('https://patreon.com/Natsumi_VRCX')">
|
||||
{{ t('dialog.change_log.donate') }}
|
||||
</Button>
|
||||
<Button @click="closeDialog">
|
||||
{{ t('dialog.change_log.close') }}
|
||||
</Button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<Dialog v-model:open="changeLogDialog.visible">
|
||||
<DialogContent class="sm:max-w-4xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.change_log.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div v-loading="!changeLogDialog.changeLog" class="changelog-dialog">
|
||||
<h2 v-text="changeLogDialog.buildName"></h2>
|
||||
<span v-show="changeLogDialog.buildName">
|
||||
{{ t('dialog.change_log.description') }}
|
||||
<a class="x-link" @click="openExternalLink('https://www.patreon.com/Natsumi_VRCX')">Patreon</a>,
|
||||
<a class="x-link" @click="openExternalLink('https://ko-fi.com/natsumi_sama')">Ko-fi</a>.
|
||||
</span>
|
||||
<VueShowdown
|
||||
:markdown="changeLogDialog.changeLog"
|
||||
flavor="github"
|
||||
:options="showdownOptions"
|
||||
@click="handleLinkClick"
|
||||
style="height: 62vh; overflow-y: auto; margin-top: 10px" />
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="mr-2"
|
||||
@click="openExternalLink('https://github.com/vrcx-team/VRCX/releases')">
|
||||
{{ t('dialog.change_log.github') }}
|
||||
</Button>
|
||||
<Button variant="outline" class="mr-2" @click="openExternalLink('https://patreon.com/Natsumi_VRCX')">
|
||||
{{ t('dialog.change_log.donate') }}
|
||||
</Button>
|
||||
<Button @click="closeDialog">
|
||||
{{ t('dialog.change_log.close') }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
@@ -1,49 +1,23 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="!!feedFiltersDialogMode"
|
||||
:title="dialogTitle"
|
||||
width="600px"
|
||||
destroy-on-close
|
||||
@close="handleDialogClose">
|
||||
<div class="toggle-list" style="height: 75vh; overflow-y: auto">
|
||||
<div v-for="setting in currentOptions" :key="setting.key" class="toggle-item">
|
||||
<span class="toggle-name"
|
||||
>{{ setting.name
|
||||
}}<TooltipWrapper
|
||||
v-if="setting.tooltip"
|
||||
side="top"
|
||||
style="margin-left: 5px"
|
||||
:content="setting.tooltip">
|
||||
<AlertTriangle v-if="setting.tooltipWarning" />
|
||||
<Info v-else />
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
<Dialog :open="!!feedFiltersDialogMode" @update:open="(open) => !open && handleDialogClose()">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ dialogTitle }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="toggle-list" style="height: 75vh; overflow-y: auto">
|
||||
<div v-for="setting in currentOptions" :key="setting.key" class="toggle-item">
|
||||
<span class="toggle-name"
|
||||
>{{ setting.name
|
||||
}}<TooltipWrapper
|
||||
v-if="setting.tooltip"
|
||||
side="top"
|
||||
style="margin-left: 5px"
|
||||
:content="setting.tooltip">
|
||||
<AlertTriangle v-if="setting.tooltipWarning" />
|
||||
<Info v-else />
|
||||
</TooltipWrapper>
|
||||
</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
|
||||
type="single"
|
||||
required
|
||||
@@ -61,22 +35,52 @@
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<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>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<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
|
||||
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>
|
||||
|
||||
<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 { computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
@@ -1,63 +1,64 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="isLaunchOptionsDialogVisible"
|
||||
:title="t('dialog.launch_options.header')"
|
||||
width="600px"
|
||||
@close="closeDialog">
|
||||
<div style="font-size: 12px">
|
||||
{{ t('dialog.launch_options.description') }} <br />
|
||||
{{ t('dialog.launch_options.example') }}
|
||||
<Badge variant="outline"
|
||||
>--fps=144 --enable-debug-gui --enable-sdk-log-levels --enable-udon-debug-logging
|
||||
</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') }}
|
||||
<Dialog :open="isLaunchOptionsDialogVisible" @update:open="(open) => (open ? null : closeDialog())">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.launch_options.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div style="font-size: 12px">
|
||||
{{ t('dialog.launch_options.description') }} <br />
|
||||
{{ t('dialog.launch_options.example') }}
|
||||
<Badge variant="outline"
|
||||
>--fps=144 --enable-debug-gui --enable-sdk-log-levels --enable-udon-debug-logging
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<InputGroupTextareaField
|
||||
v-model="launchOptionsDialog.vrcLaunchPathOverride"
|
||||
placeholder="C:\Program Files (x86)\Steam\steamapps\common\VRChat"
|
||||
:rows="1"
|
||||
style="display: block; margin-top: 10px"
|
||||
input-class="resize-none min-h-0" />
|
||||
</template>
|
||||
v-model="launchOptionsDialog.launchArguments"
|
||||
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||
:rows="2"
|
||||
placeholder=""
|
||||
style="margin-top: 10px"
|
||||
input-class="resize-none" />
|
||||
|
||||
<template #footer>
|
||||
<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') }}
|
||||
<template v-if="!isLinux">
|
||||
<div style="font-size: 12px; margin-top: 10px">
|
||||
{{ t('dialog.launch_options.path_override') }}
|
||||
</div>
|
||||
|
||||
<InputGroupTextareaField
|
||||
v-model="launchOptionsDialog.vrcLaunchPathOverride"
|
||||
placeholder="C:\Program Files (x86)\Steam\steamapps\common\VRChat"
|
||||
:rows="1"
|
||||
style="display: block; margin-top: 10px"
|
||||
input-class="resize-none min-h-0" />
|
||||
</template>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
<Button @click="updateLaunchOptions">
|
||||
{{ t('dialog.launch_options.save') }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, ref } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
|
||||
@@ -1,92 +1,93 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="isNotificationPositionDialogVisible"
|
||||
:title="t('dialog.notification_position.header')"
|
||||
width="400px"
|
||||
@close="closeDialog">
|
||||
<div style="font-size: 12px">
|
||||
{{ 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>
|
||||
<Dialog :open="isNotificationPositionDialogVisible" @update:open="(open) => (open ? null : closeDialog())">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.notification_position.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div style="font-size: 12px">
|
||||
{{ t('dialog.notification_position.description') }}
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<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>
|
||||
|
||||
<DialogFooter>
|
||||
<div style="display: flex">
|
||||
<Button @click="closeDialog">
|
||||
{{ t('dialog.notification_position.ok') }}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="ossDialog"
|
||||
:title="t('dialog.open_source.header')"
|
||||
width="650px"
|
||||
@close="closeDialog"
|
||||
destroy-on-close>
|
||||
<div v-once style="height: 350px; overflow: hidden scroll; word-break: break-all">
|
||||
<div>
|
||||
<span>{{ t('dialog.open_source.description') }}</span>
|
||||
</div>
|
||||
<Dialog :open="ossDialog" @update:open="(open) => !open && closeDialog()">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.open_source.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div v-once style="height: 350px; overflow: hidden scroll; word-break: break-all">
|
||||
<div>
|
||||
<span>{{ t('dialog.open_source.description') }}</span>
|
||||
</div>
|
||||
|
||||
<div v-for="lib in openSourceSoftwareLicenses" :key="lib.name" style="margin-top: 15px">
|
||||
<p style="font-weight: bold">{{ lib.name }}</p>
|
||||
<pre style="font-size: 12px; white-space: pre-line">{{ lib.licenseText }}</pre>
|
||||
<div v-for="lib in openSourceSoftwareLicenses" :key="lib.name" style="margin-top: 15px">
|
||||
<p style="font-weight: bold">{{ lib.name }}</p>
|
||||
<pre style="font-size: 12px; white-space: pre-line">{{ lib.licenseText }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { openSourceSoftwareLicenses } from '../../../shared/constants/ossLicenses';
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
v-model="enablePrimaryPasswordDialog.visible"
|
||||
:before-close="enablePrimaryPasswordDialog.beforeClose"
|
||||
:close-on-click-modal="false"
|
||||
:title="t('dialog.primary_password.header')"
|
||||
width="400px">
|
||||
<InputGroupField
|
||||
v-model="enablePrimaryPasswordDialog.password"
|
||||
:placeholder="t('dialog.primary_password.password_placeholder')"
|
||||
type="password"
|
||||
size="sm"
|
||||
maxlength="32"
|
||||
show-password
|
||||
autofocus />
|
||||
<InputGroupField
|
||||
v-model="enablePrimaryPasswordDialog.rePassword"
|
||||
:placeholder="t('dialog.primary_password.re_input_placeholder')"
|
||||
type="password"
|
||||
style="margin-top: 5px"
|
||||
size="sm"
|
||||
maxlength="32"
|
||||
show-password />
|
||||
<template #footer>
|
||||
<Button
|
||||
:disabled="
|
||||
enablePrimaryPasswordDialog.password.length === 0 ||
|
||||
enablePrimaryPasswordDialog.password !== enablePrimaryPasswordDialog.rePassword
|
||||
"
|
||||
@click="handleSetPrimaryPassword()">
|
||||
{{ t('dialog.primary_password.ok') }}
|
||||
</Button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<Dialog v-model:open="enablePrimaryPasswordDialog.visible">
|
||||
<DialogContent @interact-outside.prevent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.primary_password.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<InputGroupField
|
||||
v-model="enablePrimaryPasswordDialog.password"
|
||||
:placeholder="t('dialog.primary_password.password_placeholder')"
|
||||
type="password"
|
||||
size="sm"
|
||||
maxlength="32"
|
||||
show-password
|
||||
autofocus />
|
||||
<InputGroupField
|
||||
v-model="enablePrimaryPasswordDialog.rePassword"
|
||||
:placeholder="t('dialog.primary_password.re_input_placeholder')"
|
||||
type="password"
|
||||
style="margin-top: 5px"
|
||||
size="sm"
|
||||
maxlength="32"
|
||||
show-password />
|
||||
<DialogFooter>
|
||||
<Button
|
||||
:disabled="
|
||||
enablePrimaryPasswordDialog.password.length === 0 ||
|
||||
enablePrimaryPasswordDialog.password !== enablePrimaryPasswordDialog.rePassword
|
||||
"
|
||||
@click="handleSetPrimaryPassword()">
|
||||
{{ t('dialog.primary_password.ok') }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InputGroupField } from '@/components/ui/input-group';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
@@ -1,52 +1,54 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="isRegistryBackupDialogVisible"
|
||||
:title="t('dialog.registry_backup.header')"
|
||||
width="600px"
|
||||
@close="closeDialog"
|
||||
@closed="clearVrcRegistryDialog">
|
||||
<div style="margin-top: 10px">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; font-size: 12px">
|
||||
<span class="name" style="margin-right: 24px">{{ t('dialog.registry_backup.auto_backup') }}</span>
|
||||
<Switch :model-value="vrcRegistryAutoBackup" @update:modelValue="setVrcRegistryAutoBackup" />
|
||||
</div>
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
">
|
||||
<span class="name" style="margin-right: 24px">{{ t('dialog.registry_backup.ask_to_restore') }}</span>
|
||||
<Switch :model-value="vrcRegistryAskRestore" @update:modelValue="setVrcRegistryAskRestore" />
|
||||
</div>
|
||||
<DataTableLayout
|
||||
class="min-w-0 w-full"
|
||||
:table="table"
|
||||
:loading="false"
|
||||
:table-style="tableStyle"
|
||||
:show-pagination="false"
|
||||
style="margin-top: 10px" />
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 10px">
|
||||
<Button size="sm" variant="destructive" @click="deleteVrcRegistry">{{
|
||||
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')
|
||||
<Dialog :open="isRegistryBackupDialogVisible" @update:open="(open) => !open && closeAndClearDialog()">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.registry_backup.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div style="margin-top: 10px">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; font-size: 12px">
|
||||
<span class="name" style="margin-right: 24px">{{ t('dialog.registry_backup.auto_backup') }}</span>
|
||||
<Switch :model-value="vrcRegistryAutoBackup" @update:modelValue="setVrcRegistryAutoBackup" />
|
||||
</div>
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
">
|
||||
<span class="name" style="margin-right: 24px">{{
|
||||
t('dialog.registry_backup.ask_to_restore')
|
||||
}}</span>
|
||||
<Switch :model-value="vrcRegistryAskRestore" @update:modelValue="setVrcRegistryAskRestore" />
|
||||
</div>
|
||||
<DataTableLayout
|
||||
class="min-w-0 w-full"
|
||||
:table="table"
|
||||
:loading="false"
|
||||
:table-style="tableStyle"
|
||||
:show-pagination="false"
|
||||
style="margin-top: 10px" />
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 10px">
|
||||
<Button size="sm" variant="destructive" @click="deleteVrcRegistry">{{
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DataTableLayout } from '@/components/ui/data-table';
|
||||
@@ -201,7 +203,8 @@
|
||||
document.body.appendChild(fileInput);
|
||||
|
||||
fileInput.onchange = function (event) {
|
||||
const file = event.target.files[0];
|
||||
const target = /** @type {HTMLInputElement | null} */ (event.target);
|
||||
const file = target?.files?.[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
@@ -263,6 +266,13 @@
|
||||
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() {
|
||||
isRegistryBackupDialogVisible.value = false;
|
||||
}
|
||||
|
||||
@@ -1,136 +1,139 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="isTranslationApiDialogVisible"
|
||||
:title="t('dialog.translation_api.header')"
|
||||
width="450px"
|
||||
@close="closeDialog">
|
||||
<div class="options-container-item">
|
||||
<span class="name">{{ t('view.settings.appearance.appearance.bio_language') }}</span>
|
||||
<Select :model-value="bioLanguage" @update:modelValue="setBioLanguage">
|
||||
<SelectTrigger size="sm" style="float: right">
|
||||
<SelectValue :placeholder="String(getLanguageName(bioLanguage) || bioLanguage || '')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem v-for="language in languageCodes" :key="language" :value="language">
|
||||
{{ getLanguageName(language) }}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</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>
|
||||
<Dialog :open="isTranslationApiDialogVisible" @update:open="(open) => (open ? null : closeDialog())">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.translation_api.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="options-container-item">
|
||||
<span class="name">{{ t('view.settings.appearance.appearance.bio_language') }}</span>
|
||||
<Select :model-value="bioLanguage" @update:modelValue="setBioLanguage">
|
||||
<SelectTrigger size="sm" style="float: right">
|
||||
<SelectValue :placeholder="String(getLanguageName(bioLanguage) || bioLanguage || '')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem v-for="language in languageCodes" :key="language" :value="language">
|
||||
{{ getLanguageName(language) }}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<script setup>
|
||||
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 { InputGroupField, InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
import { reactive, watch } from 'vue';
|
||||
|
||||
@@ -1,176 +1,181 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="isVRChatConfigDialogVisible"
|
||||
:title="t('dialog.config_json.header')"
|
||||
width="420px"
|
||||
@close="closeDialog">
|
||||
<div v-loading="loading">
|
||||
<div style="font-size: 12px; word-break: keep-all">
|
||||
{{ t('dialog.config_json.description1') }} <br />
|
||||
{{ 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>
|
||||
<Dialog :open="isVRChatConfigDialogVisible" @update:open="(open) => (open ? null : closeDialog())">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.config_json.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div v-loading="loading">
|
||||
<div style="font-size: 12px; word-break: keep-all">
|
||||
{{ t('dialog.config_json.description1') }} <br />
|
||||
{{ t('dialog.config_json.description2') }}
|
||||
</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>
|
||||
<template #footer>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<div>
|
||||
<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
|
||||
variant="ghost"
|
||||
@click="openExternalLink('https://docs.vrchat.com/docs/configuration-file')"
|
||||
>{{ t('dialog.config_json.vrchat_docs') }}</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>
|
||||
<Button variant="secondary" class="mr-2" @click="closeDialog">{{
|
||||
t('dialog.config_json.cancel')
|
||||
}}</Button>
|
||||
<Button :disabled="loading" @click="saveVRChatConfigFile">{{
|
||||
t('dialog.config_json.save')
|
||||
|
||||
<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 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>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<DialogFooter>
|
||||
<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>
|
||||
|
||||
<script setup>
|
||||
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 { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
|
||||
@@ -1,34 +1,37 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="isYouTubeApiDialogVisible"
|
||||
:title="t('dialog.youtube_api.header')"
|
||||
width="450px"
|
||||
@close="closeDialog">
|
||||
<div style="font-size: 12px">{{ t('dialog.youtube_api.description') }} <br /></div>
|
||||
<Dialog :open="isYouTubeApiDialogVisible" @update:open="(open) => (open ? null : closeDialog())">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.youtube_api.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div style="font-size: 12px">{{ t('dialog.youtube_api.description') }} <br /></div>
|
||||
|
||||
<InputGroupTextareaField
|
||||
v-model="youTubeApiKey"
|
||||
:placeholder="t('dialog.youtube_api.placeholder')"
|
||||
:maxlength="39"
|
||||
:rows="2"
|
||||
class="mt-2.5"
|
||||
show-count />
|
||||
<InputGroupTextareaField
|
||||
v-model="youTubeApiKey"
|
||||
:placeholder="t('dialog.youtube_api.placeholder')"
|
||||
:maxlength="39"
|
||||
:rows="2"
|
||||
class="mt-2.5"
|
||||
show-count />
|
||||
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-between">
|
||||
<Button variant="outline" @click="openExternalLink('https://smashballoon.com/doc/youtube-api-key/')">
|
||||
{{ t('dialog.youtube_api.guide') }}
|
||||
</Button>
|
||||
<Button style="margin-left: auto" @click="testYouTubeApiKey">
|
||||
{{ t('dialog.youtube_api.save') }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<DialogFooter>
|
||||
<div class="flex items-center justify-between">
|
||||
<Button
|
||||
variant="outline"
|
||||
@click="openExternalLink('https://smashballoon.com/doc/youtube-api-key/')">
|
||||
{{ t('dialog.youtube_api.guide') }}
|
||||
</Button>
|
||||
<Button style="margin-left: auto" @click="testYouTubeApiKey">
|
||||
{{ t('dialog.youtube_api.save') }}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="isEditInviteMessageDialogVisible"
|
||||
:title="t('dialog.edit_invite_message.header')"
|
||||
width="400px"
|
||||
@close="closeDialog">
|
||||
<div style="font-size: 12px">
|
||||
<span>{{ t('dialog.edit_invite_message.description') }}</span>
|
||||
<InputGroupTextareaField
|
||||
v-model="message"
|
||||
:maxlength="64"
|
||||
:rows="2"
|
||||
class="mt-2.5"
|
||||
placeholder=""
|
||||
show-count />
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button variant="secondary" class="mr-2" @click="closeDialog">{{
|
||||
t('dialog.edit_invite_message.cancel')
|
||||
}}</Button>
|
||||
<Button @click="saveEditInviteMessage">{{ t('dialog.edit_invite_message.save') }}</Button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<Dialog :open="isEditInviteMessageDialogVisible" @update:open="(open) => !open && closeDialog()">
|
||||
<DialogContent class="sm:max-w-sm">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.edit_invite_message.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div style="font-size: 12px">
|
||||
<span>{{ t('dialog.edit_invite_message.description') }}</span>
|
||||
<InputGroupTextareaField
|
||||
v-model="message"
|
||||
:maxlength="64"
|
||||
:rows="2"
|
||||
class="mt-2.5"
|
||||
placeholder=""
|
||||
show-count />
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" class="mr-2" @click="closeDialog">{{
|
||||
t('dialog.edit_invite_message.cancel')
|
||||
}}</Button>
|
||||
<Button @click="saveEditInviteMessage">{{ t('dialog.edit_invite_message.save') }}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="isEditInviteMessagesDialogVisible"
|
||||
:title="t('dialog.edit_invite_messages.header')"
|
||||
width="1000px"
|
||||
@close="closeDialog">
|
||||
<TabsUnderline v-model="activeTab" :items="editInviteTabs" :unmount-on-hide="false" class="mt-2.5">
|
||||
<template #message>
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px; cursor: pointer"
|
||||
:table="inviteMessageTanstackTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleEditInviteMessageRowClick" />
|
||||
</template>
|
||||
<template #request>
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px; cursor: pointer"
|
||||
:table="inviteRequestTanstackTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleEditInviteMessageRowClick" />
|
||||
</template>
|
||||
<template #requestResponse>
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px; cursor: pointer"
|
||||
:table="inviteRequestResponseTanstackTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleEditInviteMessageRowClick" />
|
||||
</template>
|
||||
<template #response>
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px; cursor: pointer"
|
||||
:table="inviteResponseTanstackTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleEditInviteMessageRowClick" />
|
||||
</template>
|
||||
</TabsUnderline>
|
||||
</el-dialog>
|
||||
<Dialog :open="isEditInviteMessagesDialogVisible" @update:open="(open) => !open && closeDialog()">
|
||||
<DialogContent class="sm:max-w-5xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.edit_invite_messages.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<TabsUnderline v-model="activeTab" :items="editInviteTabs" :unmount-on-hide="false" class="mt-2.5">
|
||||
<template #message>
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px; cursor: pointer"
|
||||
:table="inviteMessageTanstackTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleEditInviteMessageRowClick" />
|
||||
</template>
|
||||
<template #request>
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px; cursor: pointer"
|
||||
:table="inviteRequestTanstackTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleEditInviteMessageRowClick" />
|
||||
</template>
|
||||
<template #requestResponse>
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px; cursor: pointer"
|
||||
:table="inviteRequestResponseTanstackTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleEditInviteMessageRowClick" />
|
||||
</template>
|
||||
<template #response>
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px; cursor: pointer"
|
||||
:table="inviteResponseTanstackTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleEditInviteMessageRowClick" />
|
||||
</template>
|
||||
</TabsUnderline>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<template v-if="isEditInviteMessagesDialogVisible">
|
||||
<EditInviteMessageDialog
|
||||
v-model:isEditInviteMessageDialogVisible="isEditInviteMessageDialogVisible"
|
||||
@@ -50,6 +50,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { DataTableLayout } from '@/components/ui/data-table';
|
||||
import { TabsUnderline } from '@/components/ui/tabs';
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
<template>
|
||||
<el-dialog v-model="isVisible" :title="t('dialog.export_own_avatars.header')" width="650px">
|
||||
<InputGroupTextareaField
|
||||
v-model="exportAvatarsListCsv"
|
||||
v-loading="loading"
|
||||
:rows="15"
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
input-class="resize-none"
|
||||
@click="$event.target.tagName === 'TEXTAREA' && $event.target.select()" />
|
||||
</el-dialog>
|
||||
<Dialog v-model:open="isVisible">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.export_own_avatars.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<InputGroupTextareaField
|
||||
v-model="exportAvatarsListCsv"
|
||||
v-loading="loading"
|
||||
:rows="15"
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
input-class="resize-none"
|
||||
@click="$event.target.tagName === 'TEXTAREA' && $event.target.select()" />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { useAvatarStore, useUserStore } from '../../../stores';
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="discordNamesDialogVisible"
|
||||
:title="t('dialog.discord_names.header')"
|
||||
width="650px"
|
||||
@close="closeDialog">
|
||||
<div style="font-size: 12px">
|
||||
{{ t('dialog.discord_names.description') }}
|
||||
</div>
|
||||
<InputGroupTextareaField
|
||||
v-model="discordNamesContent"
|
||||
:rows="15"
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
input-class="resize-none" />
|
||||
</el-dialog>
|
||||
<Dialog :open="discordNamesDialogVisible" @update:open="(open) => !open && closeDialog()">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.discord_names.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div style="font-size: 12px">
|
||||
{{ t('dialog.discord_names.description') }}
|
||||
</div>
|
||||
<InputGroupTextareaField
|
||||
v-model="discordNamesContent"
|
||||
:rows="15"
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
input-class="resize-none" />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { ref, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { useUserStore } from '../../../stores';
|
||||
|
||||
@@ -1,29 +1,35 @@
|
||||
<template>
|
||||
<el-dialog :title="t('dialog.export_friends_list.header')" v-model="isVisible" width="650px">
|
||||
<TabsUnderline default-value="csv" :items="exportFriendsTabs" :unmount-on-hide="false" class="mt-2.5">
|
||||
<template #csv>
|
||||
<InputGroupTextareaField
|
||||
v-model="exportFriendsListCsv"
|
||||
:rows="15"
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
input-class="resize-none"
|
||||
@click="$event.target.tagName === 'TEXTAREA' && $event.target.select()" />
|
||||
</template>
|
||||
<template #json>
|
||||
<InputGroupTextareaField
|
||||
v-model="exportFriendsListJson"
|
||||
:rows="15"
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
input-class="resize-none"
|
||||
@click="$event.target.tagName === 'TEXTAREA' && $event.target.select()" />
|
||||
</template>
|
||||
</TabsUnderline>
|
||||
</el-dialog>
|
||||
<Dialog v-model:open="isVisible">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.export_friends_list.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<TabsUnderline default-value="csv" :items="exportFriendsTabs" :unmount-on-hide="false" class="mt-2.5">
|
||||
<template #csv>
|
||||
<InputGroupTextareaField
|
||||
v-model="exportFriendsListCsv"
|
||||
:rows="15"
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
input-class="resize-none"
|
||||
@click="$event.target.tagName === 'TEXTAREA' && $event.target.select()" />
|
||||
</template>
|
||||
<template #json>
|
||||
<InputGroupTextareaField
|
||||
v-model="exportFriendsListJson"
|
||||
:rows="15"
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
input-class="resize-none"
|
||||
@click="$event.target.tagName === 'TEXTAREA' && $event.target.select()" />
|
||||
</template>
|
||||
</TabsUnderline>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
import { TabsUnderline } from '@/components/ui/tabs';
|
||||
|
||||
@@ -1,119 +1,124 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="visible"
|
||||
:title="t('dialog.group_calendar.header')"
|
||||
width="90vw"
|
||||
height="80vh"
|
||||
@close="closeDialog">
|
||||
<template #header>
|
||||
<div class="dialog-title-container">
|
||||
<span>{{ t('dialog.group_calendar.header') }}</span>
|
||||
<Button size="sm" variant="outline" @click="toggleViewMode" class="view-toggle-btn">
|
||||
{{
|
||||
viewMode === 'timeline'
|
||||
? t('dialog.group_calendar.list_view')
|
||||
: t('dialog.group_calendar.calendar_view')
|
||||
}}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="featured-switch">
|
||||
<span class="featured-switch-text">{{ t('dialog.group_calendar.featured_events') }}</span>
|
||||
<Switch v-model="showFeaturedEvents" @update:modelValue="toggleFeaturedEvents" />
|
||||
</div>
|
||||
</template>
|
||||
<div class="top-content">
|
||||
<transition name="el-fade-in-linear" mode="out-in">
|
||||
<div v-if="viewMode === 'timeline'" key="timeline" class="timeline-view">
|
||||
<div class="timeline-container">
|
||||
<el-timeline v-if="groupedTimelineEvents.length">
|
||||
<el-timeline-item
|
||||
v-for="(timeGroup, key) of groupedTimelineEvents"
|
||||
:key="key"
|
||||
:timestamp="dayjs(timeGroup.startsAt).format('MM-DD ddd') + ' ' + timeGroup.startTime"
|
||||
placement="top">
|
||||
<div class="time-group-container">
|
||||
<GroupCalendarEventCard
|
||||
v-for="value in timeGroup.events"
|
||||
:key="value.id"
|
||||
:event="value"
|
||||
mode="timeline"
|
||||
:is-following="isEventFollowing(value.id)"
|
||||
:card-class="{ 'grouped-card': timeGroup.events.length > 1 }"
|
||||
@update-following-calendar-data="updateFollowingCalendarData"
|
||||
@click-action="showGroupDialog(value.ownerId)" />
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
<div v-else>{{ t('dialog.group_calendar.no_events') }}</div>
|
||||
</div>
|
||||
<Dialog :open="visible" @update:open="(open) => (open ? null : closeDialog())">
|
||||
<DialogContent class="x-dialog w-[90vw] max-w-[90vw] h-[80vh] overflow-hidden">
|
||||
<DialogHeader>
|
||||
<div class="dialog-title-container">
|
||||
<DialogTitle>{{ t('dialog.group_calendar.header') }}</DialogTitle>
|
||||
<Button size="sm" variant="outline" @click="toggleViewMode" class="view-toggle-btn">
|
||||
{{
|
||||
viewMode === 'timeline'
|
||||
? t('dialog.group_calendar.list_view')
|
||||
: t('dialog.group_calendar.calendar_view')
|
||||
}}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="featured-switch">
|
||||
<span class="featured-switch-text">{{ t('dialog.group_calendar.featured_events') }}</span>
|
||||
<Switch v-model="showFeaturedEvents" @update:modelValue="toggleFeaturedEvents" />
|
||||
</div>
|
||||
</DialogHeader>
|
||||
<div class="top-content">
|
||||
<transition name="el-fade-in-linear" mode="out-in">
|
||||
<div v-if="viewMode === 'timeline'" key="timeline" class="timeline-view">
|
||||
<div class="timeline-container">
|
||||
<el-timeline v-if="groupedTimelineEvents.length">
|
||||
<el-timeline-item
|
||||
v-for="(timeGroup, key) of groupedTimelineEvents"
|
||||
:key="key"
|
||||
:timestamp="
|
||||
dayjs(timeGroup.startsAt).format('MM-DD ddd') + ' ' + timeGroup.startTime
|
||||
"
|
||||
placement="top">
|
||||
<div class="time-group-container">
|
||||
<GroupCalendarEventCard
|
||||
v-for="value in timeGroup.events"
|
||||
:key="value.id"
|
||||
:event="value"
|
||||
mode="timeline"
|
||||
:is-following="isEventFollowing(value.id)"
|
||||
:card-class="{ 'grouped-card': timeGroup.events.length > 1 }"
|
||||
@update-following-calendar-data="updateFollowingCalendarData"
|
||||
@click-action="showGroupDialog(value.ownerId)" />
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
<div v-else>{{ t('dialog.group_calendar.no_events') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="calendar-container">
|
||||
<el-calendar v-model="selectedDay" v-loading="isLoading">
|
||||
<template #date-cell="{ data }">
|
||||
<div class="date">
|
||||
<div
|
||||
class="calendar-date-content"
|
||||
:class="{ 'has-events': filteredCalendar[formatDateKey(data.date)]?.length }">
|
||||
{{ dayjs(data.date).format('D') }}
|
||||
<div class="calendar-container">
|
||||
<el-calendar v-model="selectedDay" v-loading="isLoading">
|
||||
<template #date-cell="{ data }">
|
||||
<div class="date">
|
||||
<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 }}
|
||||
class="calendar-date-content"
|
||||
:class="{
|
||||
'has-events': 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>
|
||||
</template>
|
||||
</el-calendar>
|
||||
</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" />
|
||||
</template>
|
||||
</el-calendar>
|
||||
</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 v-if="filteredGroupEvents.length" class="groups-container">
|
||||
<div v-for="group in filteredGroupEvents" :key="group.groupId" class="group-row">
|
||||
<div class="group-header" @click="toggleGroup(group.groupId)">
|
||||
<ArrowRight
|
||||
class="rotation-transition"
|
||||
:class="{ rotate: !groupCollapsed[group.groupId] }" />
|
||||
{{ group.groupName }}
|
||||
</div>
|
||||
<div class="events-row" v-show="!groupCollapsed[group.groupId]">
|
||||
<GroupCalendarEventCard
|
||||
v-for="event in group.events"
|
||||
:key="event.id"
|
||||
:event="event"
|
||||
mode="grid"
|
||||
:is-following="isEventFollowing(event.id)"
|
||||
@update-following-calendar-data="updateFollowingCalendarData"
|
||||
@click-action="showGroupDialog(event.ownerId)"
|
||||
card-class="grid-card" />
|
||||
<div class="groups-grid" v-loading="isLoading">
|
||||
<div v-if="filteredGroupEvents.length" class="groups-container">
|
||||
<div v-for="group in filteredGroupEvents" :key="group.groupId" class="group-row">
|
||||
<div class="group-header" @click="toggleGroup(group.groupId)">
|
||||
<ArrowRight
|
||||
class="rotation-transition"
|
||||
:class="{ rotate: !groupCollapsed[group.groupId] }" />
|
||||
{{ group.groupName }}
|
||||
</div>
|
||||
<div class="events-row" v-show="!groupCollapsed[group.groupId]">
|
||||
<GroupCalendarEventCard
|
||||
v-for="event in group.events"
|
||||
:key="event.id"
|
||||
:event="event"
|
||||
mode="grid"
|
||||
:is-following="isEventFollowing(event.id)"
|
||||
@update-following-calendar-data="updateFollowingCalendarData"
|
||||
@click-action="showGroupDialog(event.ownerId)"
|
||||
card-class="grid-card" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-events">
|
||||
{{
|
||||
searchQuery
|
||||
? t('dialog.group_calendar.search_no_matching')
|
||||
: t('dialog.group_calendar.search_no_this_month')
|
||||
}}
|
||||
<div v-else class="no-events">
|
||||
{{
|
||||
searchQuery
|
||||
? t('dialog.group_calendar.search_no_matching')
|
||||
: t('dialog.group_calendar.search_no_this_month')
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</transition>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { ArrowRight } from 'lucide-vue-next';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
@@ -1,68 +1,69 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:model-value="isNoteExportDialogVisible"
|
||||
:title="t('dialog.note_export.header')"
|
||||
width="1000px"
|
||||
@close="closeDialog">
|
||||
<div style="font-size: 12px">
|
||||
{{ t('dialog.note_export.description1') }} <br />
|
||||
{{ t('dialog.note_export.description2') }} <br />
|
||||
{{ t('dialog.note_export.description3') }} <br />
|
||||
{{ t('dialog.note_export.description4') }} <br />
|
||||
{{ t('dialog.note_export.description5') }} <br />
|
||||
{{ t('dialog.note_export.description6') }} <br />
|
||||
{{ t('dialog.note_export.description7') }} <br />
|
||||
{{ t('dialog.note_export.description8') }} <br />
|
||||
</div>
|
||||
<Dialog :open="isNoteExportDialogVisible" @update:open="(open) => !open && closeDialog()">
|
||||
<DialogContent class="sm:max-w-5xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('dialog.note_export.header') }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div style="font-size: 12px">
|
||||
{{ t('dialog.note_export.description1') }} <br />
|
||||
{{ t('dialog.note_export.description2') }} <br />
|
||||
{{ t('dialog.note_export.description3') }} <br />
|
||||
{{ t('dialog.note_export.description4') }} <br />
|
||||
{{ t('dialog.note_export.description5') }} <br />
|
||||
{{ t('dialog.note_export.description6') }} <br />
|
||||
{{ t('dialog.note_export.description7') }} <br />
|
||||
{{ t('dialog.note_export.description8') }} <br />
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
class="mr-2"
|
||||
variant="outline"
|
||||
:disabled="loading"
|
||||
style="margin-top: 10px"
|
||||
@click="updateNoteExportDialog">
|
||||
{{ 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
|
||||
size="sm"
|
||||
class="mr-2"
|
||||
variant="outline"
|
||||
:disabled="loading"
|
||||
style="margin-top: 10px"
|
||||
@click="updateNoteExportDialog">
|
||||
{{ t('dialog.note_export.refresh') }}
|
||||
</Button>
|
||||
<h2 style="font-weight: bold; margin: 0">
|
||||
{{ t('dialog.note_export.errors') }}
|
||||
</h2>
|
||||
<pre style="white-space: pre-wrap; font-size: 12px" v-text="errors"></pre>
|
||||
</template>
|
||||
<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>
|
||||
|
||||
<DataTableLayout
|
||||
class="min-w-0 w-full"
|
||||
:table="table"
|
||||
:loading="loading"
|
||||
:table-style="tableStyle"
|
||||
:show-pagination="false"
|
||||
style="margin-top: 10px" />
|
||||
</el-dialog>
|
||||
<template v-if="errors">
|
||||
<Button size="sm" variant="outline" @click="errors = ''">
|
||||
{{ t('dialog.note_export.clear_errors') }}
|
||||
</Button>
|
||||
<h2 style="font-weight: bold; margin: 0">
|
||||
{{ t('dialog.note_export.errors') }}
|
||||
</h2>
|
||||
<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>
|
||||
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DataTableLayout } from '@/components/ui/data-table';
|
||||
|
||||
Reference in New Issue
Block a user