replace el-dialog

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

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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;
}

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';