mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-09-12 10:34:24 +08:00
fix: 完善 MSD 错误提示;解决 MSD 文件名称过长问题;优化粘贴文本样式;优化所有下拉框样式;删除“画面静止时会自动降帧”提示
This commit is contained in:
@@ -7,7 +7,7 @@ import { Loader2, RefreshCw, Volume2 } from 'lucide-vue-next'
|
|||||||
import { audioApi, configApi } from '@/api'
|
import { audioApi, configApi } from '@/api'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Label } from '@/components/ui/label'
|
import { Label } from '@/components/ui/label'
|
||||||
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
@@ -48,6 +48,7 @@ const applying = ref(false)
|
|||||||
const audioEnabled = ref(false)
|
const audioEnabled = ref(false)
|
||||||
const selectedDevice = ref('')
|
const selectedDevice = ref('')
|
||||||
const selectedQuality = ref<'voice' | 'balanced' | 'high'>('balanced')
|
const selectedQuality = ref<'voice' | 'balanced' | 'high'>('balanced')
|
||||||
|
const EMPTY_SELECT_VALUE = '__one-kvm-empty-select-value__'
|
||||||
|
|
||||||
async function handleVolumeChange(value: number[] | undefined) {
|
async function handleVolumeChange(value: number[] | undefined) {
|
||||||
if (!value || value.length === 0 || value[0] === undefined) return
|
if (!value || value.length === 0 || value[0] === undefined) return
|
||||||
@@ -226,23 +227,26 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label class="text-xs text-muted-foreground">{{ t('actionbar.audioDevice') }}</Label>
|
<Label class="text-xs text-muted-foreground">{{ t('actionbar.audioDevice') }}</Label>
|
||||||
<NativeSelect
|
<Select
|
||||||
:model-value="selectedDevice"
|
:model-value="selectedDevice"
|
||||||
:disabled="loadingDevices || devices.length === 0"
|
:disabled="loadingDevices || devices.length === 0"
|
||||||
size="sm"
|
@update:model-value="selectedDevice = $event === EMPTY_SELECT_VALUE ? '' : String($event)"
|
||||||
class="w-full text-xs"
|
|
||||||
@update:model-value="selectedDevice = $event as string"
|
|
||||||
>
|
>
|
||||||
<NativeSelectOption value="">{{ t('actionbar.selectAudioDevice') }}</NativeSelectOption>
|
<SelectTrigger size="sm" class="w-full text-xs">
|
||||||
<NativeSelectOption
|
<SelectValue :placeholder="t('actionbar.selectAudioDevice')" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="EMPTY_SELECT_VALUE" class="text-xs">{{ t('actionbar.selectAudioDevice') }}</SelectItem>
|
||||||
|
<SelectItem
|
||||||
v-for="device in devices"
|
v-for="device in devices"
|
||||||
:key="device.name"
|
:key="device.name"
|
||||||
:value="device.name"
|
:value="device.name"
|
||||||
class="text-xs"
|
class="text-xs"
|
||||||
>
|
>
|
||||||
{{ device.description || device.name }}
|
{{ device.description || device.name }}
|
||||||
</NativeSelectOption>
|
</SelectItem>
|
||||||
</NativeSelect>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { toast } from 'vue-sonner'
|
|||||||
import { Loader2, Mic, MicOff, RefreshCw } from 'lucide-vue-next'
|
import { Loader2, Mic, MicOff, RefreshCw } from 'lucide-vue-next'
|
||||||
|
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||||
import { getMicrophone } from '@/composables/useMicrophone'
|
import { getMicrophone } from '@/composables/useMicrophone'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -69,25 +69,25 @@ async function selectDevice(value: unknown) {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<NativeSelect
|
<Select
|
||||||
:model-value="microphone.selectedDeviceId.value"
|
:model-value="microphone.selectedDeviceId.value"
|
||||||
:disabled="microphone.loadingDevices.value || microphone.busy.value || microphone.inputDevices.value.length === 0"
|
:disabled="microphone.loadingDevices.value || microphone.busy.value || microphone.inputDevices.value.length === 0"
|
||||||
size="sm"
|
|
||||||
class="w-full text-xs"
|
|
||||||
@update:model-value="selectDevice"
|
@update:model-value="selectDevice"
|
||||||
>
|
>
|
||||||
<NativeSelectOption v-if="microphone.inputDevices.value.length === 0" value="">
|
<SelectTrigger size="sm" class="w-full text-xs">
|
||||||
{{ t('actionbar.noMicrophoneDevices') }}
|
<SelectValue :placeholder="t('actionbar.noMicrophoneDevices')" />
|
||||||
</NativeSelectOption>
|
</SelectTrigger>
|
||||||
<NativeSelectOption
|
<SelectContent>
|
||||||
|
<SelectItem
|
||||||
v-for="device in microphone.inputDevices.value"
|
v-for="device in microphone.inputDevices.value"
|
||||||
:key="device.deviceId"
|
:key="device.deviceId"
|
||||||
:value="device.deviceId"
|
:value="device.deviceId"
|
||||||
class="text-xs"
|
class="text-xs"
|
||||||
>
|
>
|
||||||
{{ device.label }}
|
{{ device.label }}
|
||||||
</NativeSelectOption>
|
</SelectItem>
|
||||||
</NativeSelect>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Textarea } from '@/components/ui/textarea'
|
import { Textarea } from '@/components/ui/textarea'
|
||||||
import { Label } from '@/components/ui/label'
|
|
||||||
import { Progress } from '@/components/ui/progress'
|
import { Progress } from '@/components/ui/progress'
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||||
import { CornerDownLeft, Square, AlertCircle } from 'lucide-vue-next'
|
import { CornerDownLeft, Square, AlertCircle } from 'lucide-vue-next'
|
||||||
@@ -182,24 +181,19 @@ function handleKeydown(e: KeyboardEvent) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="p-4 space-y-4">
|
<div class="p-4 space-y-4">
|
||||||
<div class="space-y-1">
|
|
||||||
<h3 class="font-semibold text-sm">{{ t('paste.title') }}</h3>
|
<h3 class="font-semibold text-sm">{{ t('paste.title') }}</h3>
|
||||||
<p class="text-xs text-muted-foreground">{{ t('paste.description') }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-2">
|
|
||||||
<Label for="paste-text">{{ t('paste.label') }}</Label>
|
|
||||||
<Textarea
|
<Textarea
|
||||||
id="paste-text"
|
id="paste-text"
|
||||||
ref="textareaRef"
|
ref="textareaRef"
|
||||||
v-model="text"
|
v-model="text"
|
||||||
|
:aria-label="t('paste.title')"
|
||||||
:placeholder="t('paste.placeholder')"
|
:placeholder="t('paste.placeholder')"
|
||||||
class="min-h-[120px] resize-none font-mono text-sm"
|
class="min-h-[120px] resize-none text-sm"
|
||||||
:disabled="isPasting"
|
:disabled="isPasting"
|
||||||
@keydown="handleKeydown"
|
@keydown="handleKeydown"
|
||||||
@keyup.stop
|
@keyup.stop
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Warning for untypable characters -->
|
<!-- Warning for untypable characters -->
|
||||||
<Alert v-if="hasUntypableChars && !isPasting" variant="warning">
|
<Alert v-if="hasUntypableChars && !isPasting" variant="warning">
|
||||||
|
|||||||
@@ -184,8 +184,6 @@ export default {
|
|||||||
},
|
},
|
||||||
paste: {
|
paste: {
|
||||||
title: 'Paste Text',
|
title: 'Paste Text',
|
||||||
description: 'Enter text to send to remote host as keyboard input',
|
|
||||||
label: 'Text Content',
|
|
||||||
placeholder: 'Enter text to paste here...',
|
placeholder: 'Enter text to paste here...',
|
||||||
hint: 'Press Ctrl+Enter to send quickly',
|
hint: 'Press Ctrl+Enter to send quickly',
|
||||||
confirm: 'Send',
|
confirm: 'Send',
|
||||||
@@ -945,7 +943,6 @@ export default {
|
|||||||
fps: 'FPS',
|
fps: 'FPS',
|
||||||
fpsTarget: 'Target FPS',
|
fpsTarget: 'Target FPS',
|
||||||
fpsActual: 'Actual FPS',
|
fpsActual: 'Actual FPS',
|
||||||
fpsStaticHint: 'Frame rate drops automatically while the image is static',
|
|
||||||
paused: 'Paused',
|
paused: 'Paused',
|
||||||
backend: 'Backend',
|
backend: 'Backend',
|
||||||
currentMode: 'Current Mode',
|
currentMode: 'Current Mode',
|
||||||
|
|||||||
@@ -184,8 +184,6 @@ export default {
|
|||||||
},
|
},
|
||||||
paste: {
|
paste: {
|
||||||
title: '粘贴文本',
|
title: '粘贴文本',
|
||||||
description: '输入要发送到远程主机的文本,将以键盘输入方式发送',
|
|
||||||
label: '文本内容',
|
|
||||||
placeholder: '在此输入要粘贴的文本...',
|
placeholder: '在此输入要粘贴的文本...',
|
||||||
hint: '按 Ctrl+Enter 快速发送',
|
hint: '按 Ctrl+Enter 快速发送',
|
||||||
confirm: '发送',
|
confirm: '发送',
|
||||||
@@ -944,7 +942,6 @@ export default {
|
|||||||
fps: '帧率',
|
fps: '帧率',
|
||||||
fpsTarget: '目标帧率',
|
fpsTarget: '目标帧率',
|
||||||
fpsActual: '实际帧率',
|
fpsActual: '实际帧率',
|
||||||
fpsStaticHint: '画面静止时会自动降帧',
|
|
||||||
paused: '已暂停',
|
paused: '已暂停',
|
||||||
backend: '后端',
|
backend: '后端',
|
||||||
currentMode: '当前模式',
|
currentMode: '当前模式',
|
||||||
|
|||||||
@@ -308,13 +308,6 @@ const videoDetails = computed<StatusDetail[]>(() => {
|
|||||||
{ label: t('statusCard.fpsActual'), value: actualFpsValue, status: actualStatus },
|
{ label: t('statusCard.fpsActual'), value: actualFpsValue, status: actualStatus },
|
||||||
]
|
]
|
||||||
|
|
||||||
if (videoMode.value === 'mjpeg' && !paused && receivedFps > 0 && receivedFps < (stream.targetFps ?? 0)) {
|
|
||||||
details.push({
|
|
||||||
label: '',
|
|
||||||
value: t('statusCard.fpsStaticHint'),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return details
|
return details
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ import { Label } from '@/components/ui/label'
|
|||||||
import { Switch } from '@/components/ui/switch'
|
import { Switch } from '@/components/ui/switch'
|
||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
|
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||||
import { ButtonGroup } from '@/components/ui/button-group'
|
import { ButtonGroup } from '@/components/ui/button-group'
|
||||||
@@ -151,6 +150,7 @@ const configStore = useConfigStore()
|
|||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const featureVisibility = useFeatureVisibility()
|
const featureVisibility = useFeatureVisibility()
|
||||||
const { theme, setTheme } = useTheme()
|
const { theme, setTheme } = useTheme()
|
||||||
|
const EMPTY_SELECT_VALUE = '__one-kvm-empty-select-value__'
|
||||||
|
|
||||||
const isWindows = computed(() => systemStore.platform?.mode === 'windows')
|
const isWindows = computed(() => systemStore.platform?.mode === 'windows')
|
||||||
|
|
||||||
@@ -2927,38 +2927,57 @@ watch(isWindows, () => {
|
|||||||
<CardContent class="space-y-4">
|
<CardContent class="space-y-4">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="video-device">{{ t('settings.videoDevice') }}</Label>
|
<Label for="video-device">{{ t('settings.videoDevice') }}</Label>
|
||||||
<NativeSelect id="video-device" v-model="config.video_device" class="w-full">
|
<Select
|
||||||
<NativeSelectOption value="">{{ t('settings.selectDevice') }}</NativeSelectOption>
|
:model-value="config.video_device"
|
||||||
<NativeSelectOption v-for="dev in devices.video" :key="dev.path" :value="dev.path">{{ formatVideoDeviceLabel(dev) }}</NativeSelectOption>
|
@update:model-value="value => config.video_device = value === EMPTY_SELECT_VALUE ? '' : String(value)"
|
||||||
</NativeSelect>
|
>
|
||||||
|
<SelectTrigger id="video-device" class="w-full"><SelectValue :placeholder="t('settings.selectDevice')" /></SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.selectDevice') }}</SelectItem>
|
||||||
|
<SelectItem v-for="dev in devices.video" :key="dev.path" :value="dev.path">{{ formatVideoDeviceLabel(dev) }}</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="video-format">{{ t('settings.videoFormat') }}</Label>
|
<Label for="video-format">{{ t('settings.videoFormat') }}</Label>
|
||||||
<NativeSelect id="video-format" v-model="config.video_format" class="w-full" :disabled="!config.video_device">
|
<Select
|
||||||
<NativeSelectOption value="">{{ t('settings.selectFormat') }}</NativeSelectOption>
|
:model-value="config.video_format"
|
||||||
<NativeSelectOption
|
:disabled="!config.video_device"
|
||||||
|
@update:model-value="value => config.video_format = value === EMPTY_SELECT_VALUE ? '' : String(value)"
|
||||||
|
>
|
||||||
|
<SelectTrigger id="video-format" class="w-full"><SelectValue :placeholder="t('settings.selectFormat')" /></SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.selectFormat') }}</SelectItem>
|
||||||
|
<SelectItem
|
||||||
v-for="fmt in availableFormatOptions"
|
v-for="fmt in availableFormatOptions"
|
||||||
:key="fmt.format"
|
:key="fmt.format"
|
||||||
:value="fmt.format"
|
:value="fmt.format"
|
||||||
:disabled="fmt.disabled"
|
:disabled="fmt.disabled"
|
||||||
>
|
>
|
||||||
{{ fmt.format }} - {{ fmt.description }}{{ fmt.disabled ? t('common.notSupportedYet') : '' }}
|
{{ fmt.format }} - {{ fmt.description }}{{ fmt.disabled ? t('common.notSupportedYet') : '' }}
|
||||||
</NativeSelectOption>
|
</SelectItem>
|
||||||
</NativeSelect>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid gap-4 sm:grid-cols-2">
|
<div class="grid gap-4 sm:grid-cols-2">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="video-resolution">{{ t('settings.resolution') }}</Label>
|
<Label for="video-resolution">{{ t('settings.resolution') }}</Label>
|
||||||
<NativeSelect id="video-resolution" :model-value="`${config.video_width}x${config.video_height}`" class="w-full" :disabled="!config.video_format" @update:model-value="value => { const parts = String(value).split('x').map(Number); if (parts[0] && parts[1]) { config.video_width = parts[0]; config.video_height = parts[1]; } }">
|
<Select :model-value="`${config.video_width}x${config.video_height}`" :disabled="!config.video_format" @update:model-value="value => { const parts = String(value).split('x').map(Number); if (parts[0] && parts[1]) { config.video_width = parts[0]; config.video_height = parts[1]; } }">
|
||||||
<NativeSelectOption v-for="res in availableResolutions" :key="`${res.width}x${res.height}`" :value="`${res.width}x${res.height}`">{{ res.width }}x{{ res.height }}</NativeSelectOption>
|
<SelectTrigger id="video-resolution" class="w-full"><SelectValue /></SelectTrigger>
|
||||||
</NativeSelect>
|
<SelectContent>
|
||||||
|
<SelectItem v-for="res in availableResolutions" :key="`${res.width}x${res.height}`" :value="`${res.width}x${res.height}`">{{ res.width }}x{{ res.height }}</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="video-fps">{{ t('settings.frameRate') }}</Label>
|
<Label for="video-fps">{{ t('settings.frameRate') }}</Label>
|
||||||
<NativeSelect id="video-fps" :model-value="config.video_fps" class="w-full" :disabled="!config.video_format" @update:model-value="value => config.video_fps = Number(value)">
|
<Select :model-value="config.video_fps" :disabled="!config.video_format" @update:model-value="value => config.video_fps = Number(value)">
|
||||||
<NativeSelectOption v-for="fps in availableFps" :key="fps" :value="fps">{{ formatFpsLabel(fps) }}</NativeSelectOption>
|
<SelectTrigger id="video-fps" class="w-full"><SelectValue /></SelectTrigger>
|
||||||
<NativeSelectOption v-if="!availableFps.includes(config.video_fps)" :value="config.video_fps">{{ formatFpsLabel(config.video_fps) }}</NativeSelectOption>
|
<SelectContent>
|
||||||
</NativeSelect>
|
<SelectItem v-for="fps in availableFps" :key="fps" :value="fps">{{ formatFpsLabel(fps) }}</SelectItem>
|
||||||
|
<SelectItem v-if="!availableFps.includes(config.video_fps)" :value="config.video_fps">{{ formatFpsLabel(config.video_fps) }}</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -3077,10 +3096,16 @@ watch(isWindows, () => {
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="config.hid_backend === 'ch9329'" class="space-y-2">
|
<div v-if="config.hid_backend === 'ch9329'" class="space-y-2">
|
||||||
<Label for="serial-device">{{ t('settings.serialDevice') }}</Label>
|
<Label for="serial-device">{{ t('settings.serialDevice') }}</Label>
|
||||||
<NativeSelect id="serial-device" v-model="config.hid_serial_device" class="w-full">
|
<Select
|
||||||
<NativeSelectOption value="">{{ t('settings.selectDevice') }}</NativeSelectOption>
|
:model-value="config.hid_serial_device"
|
||||||
<NativeSelectOption v-for="dev in devices.serial" :key="dev.path" :value="dev.path">{{ dev.name }} ({{ dev.path }})</NativeSelectOption>
|
@update:model-value="value => config.hid_serial_device = value === EMPTY_SELECT_VALUE ? '' : String(value)"
|
||||||
</NativeSelect>
|
>
|
||||||
|
<SelectTrigger id="serial-device" class="w-full"><SelectValue :placeholder="t('settings.selectDevice')" /></SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.selectDevice') }}</SelectItem>
|
||||||
|
<SelectItem v-for="dev in devices.serial" :key="dev.path" :value="dev.path">{{ dev.name }} ({{ dev.path }})</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="config.hid_backend === 'ch9329'" class="space-y-2">
|
<div v-if="config.hid_backend === 'ch9329'" class="space-y-2">
|
||||||
<Label for="serial-baudrate">{{ t('settings.baudRate') }}</Label>
|
<Label for="serial-baudrate">{{ t('settings.baudRate') }}</Label>
|
||||||
@@ -3093,10 +3118,16 @@ watch(isWindows, () => {
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="config.hid_backend === 'otg'" class="space-y-2">
|
<div v-if="config.hid_backend === 'otg'" class="space-y-2">
|
||||||
<Label for="otg-udc">{{ t('settings.otgUdc') }}</Label>
|
<Label for="otg-udc">{{ t('settings.otgUdc') }}</Label>
|
||||||
<NativeSelect id="otg-udc" v-model="config.hid_otg_udc" class="w-full">
|
<Select
|
||||||
<NativeSelectOption value="">{{ t('settings.autoRecommended') }}</NativeSelectOption>
|
:model-value="config.hid_otg_udc"
|
||||||
<NativeSelectOption v-for="udc in devices.udc" :key="udc.name" :value="udc.name">{{ udc.name }}</NativeSelectOption>
|
@update:model-value="value => config.hid_otg_udc = value === EMPTY_SELECT_VALUE ? '' : String(value)"
|
||||||
</NativeSelect>
|
>
|
||||||
|
<SelectTrigger id="otg-udc" class="w-full"><SelectValue :placeholder="t('settings.autoRecommended')" /></SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.autoRecommended') }}</SelectItem>
|
||||||
|
<SelectItem v-for="udc in devices.udc" :key="udc.name" :value="udc.name">{{ udc.name }}</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="config.hid_backend === 'ch9329'">
|
<template v-if="config.hid_backend === 'ch9329'">
|
||||||
@@ -3343,30 +3374,37 @@ watch(isWindows, () => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="otg-network-interface">{{ t('settings.otgNetworkInterface') }}</Label>
|
<Label for="otg-network-interface">{{ t('settings.otgNetworkInterface') }}</Label>
|
||||||
<NativeSelect
|
<Select
|
||||||
id="otg-network-interface"
|
:model-value="config.otg_network_interface"
|
||||||
v-model="config.otg_network_interface"
|
|
||||||
class="w-full"
|
|
||||||
:disabled="otgNetworkInterfaces.length === 0"
|
:disabled="otgNetworkInterfaces.length === 0"
|
||||||
|
@update:model-value="value => config.otg_network_interface = value === EMPTY_SELECT_VALUE ? '' : String(value)"
|
||||||
|
>
|
||||||
|
<SelectTrigger
|
||||||
|
id="otg-network-interface"
|
||||||
|
class="w-full"
|
||||||
:aria-invalid="Boolean(otgNetworkInterfacesError)"
|
:aria-invalid="Boolean(otgNetworkInterfacesError)"
|
||||||
>
|
>
|
||||||
<NativeSelectOption
|
<SelectValue :placeholder="otgNetworkInterfacesError || t('settings.otgNetworkNone')" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem
|
||||||
v-if="otgNetworkInterfacesError"
|
v-if="otgNetworkInterfacesError"
|
||||||
:value="config.otg_network_interface"
|
:value="config.otg_network_interface || EMPTY_SELECT_VALUE"
|
||||||
>
|
>
|
||||||
{{ otgNetworkInterfacesError }}
|
{{ otgNetworkInterfacesError }}
|
||||||
</NativeSelectOption>
|
</SelectItem>
|
||||||
<NativeSelectOption v-else-if="otgNetworkInterfaces.length === 0" value="">
|
<SelectItem v-else-if="otgNetworkInterfaces.length === 0" :value="EMPTY_SELECT_VALUE">
|
||||||
{{ t('settings.otgNetworkNone') }}
|
{{ t('settings.otgNetworkNone') }}
|
||||||
</NativeSelectOption>
|
</SelectItem>
|
||||||
<NativeSelectOption
|
<SelectItem
|
||||||
v-for="item in otgNetworkInterfaces"
|
v-for="item in otgNetworkInterfaces"
|
||||||
:key="item.name"
|
:key="item.name"
|
||||||
:value="item.name"
|
:value="item.name"
|
||||||
>
|
>
|
||||||
{{ item.name }} · {{ item.interface_type }}
|
{{ item.name }} · {{ item.interface_type }}
|
||||||
</NativeSelectOption>
|
</SelectItem>
|
||||||
</NativeSelect>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -4058,17 +4096,23 @@ watch(isWindows, () => {
|
|||||||
<div v-if="['usbrelay', 'serial'].includes(atxConfig.driver)" class="grid gap-4 sm:grid-cols-2">
|
<div v-if="['usbrelay', 'serial'].includes(atxConfig.driver)" class="grid gap-4 sm:grid-cols-2">
|
||||||
<div v-if="['usbrelay', 'serial'].includes(atxConfig.driver)" class="space-y-2">
|
<div v-if="['usbrelay', 'serial'].includes(atxConfig.driver)" class="space-y-2">
|
||||||
<Label for="atx-device">{{ t('settings.atxDevice') }}</Label>
|
<Label for="atx-device">{{ t('settings.atxDevice') }}</Label>
|
||||||
<NativeSelect id="atx-device" v-model="atxConfig.device" class="w-full">
|
<Select
|
||||||
<NativeSelectOption value="">{{ t('settings.selectDevice') }}</NativeSelectOption>
|
:model-value="atxConfig.device"
|
||||||
<NativeSelectOption
|
@update:model-value="value => atxConfig.device = value === EMPTY_SELECT_VALUE ? '' : String(value)"
|
||||||
|
>
|
||||||
|
<SelectTrigger id="atx-device" class="w-full"><SelectValue :placeholder="t('settings.selectDevice')" /></SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.selectDevice') }}</SelectItem>
|
||||||
|
<SelectItem
|
||||||
v-for="dev in getAtxDevicesForDriver(atxConfig.driver)"
|
v-for="dev in getAtxDevicesForDriver(atxConfig.driver)"
|
||||||
:key="dev"
|
:key="dev"
|
||||||
:value="dev"
|
:value="dev"
|
||||||
:disabled="atxConfig.driver === 'serial' && isAtxSerialDeviceReserved(dev)"
|
:disabled="atxConfig.driver === 'serial' && isAtxSerialDeviceReserved(dev)"
|
||||||
>
|
>
|
||||||
{{ formatAtxDeviceLabel(atxConfig.driver, dev) }}
|
{{ formatAtxDeviceLabel(atxConfig.driver, dev) }}
|
||||||
</NativeSelectOption>
|
</SelectItem>
|
||||||
</NativeSelect>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="atxConfig.driver === 'serial'" class="space-y-2">
|
<div v-if="atxConfig.driver === 'serial'" class="space-y-2">
|
||||||
<Label for="atx-baudrate">{{ t('settings.baudRate') }}</Label>
|
<Label for="atx-baudrate">{{ t('settings.baudRate') }}</Label>
|
||||||
@@ -4089,10 +4133,16 @@ watch(isWindows, () => {
|
|||||||
<div class="grid gap-3 sm:grid-cols-3">
|
<div class="grid gap-3 sm:grid-cols-3">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="power-device">{{ t('settings.atxGpioChip') }}</Label>
|
<Label for="power-device">{{ t('settings.atxGpioChip') }}</Label>
|
||||||
<NativeSelect id="power-device" v-model="atxConfig.power.device" class="w-full">
|
<Select
|
||||||
<NativeSelectOption value="">{{ t('settings.atxDriverNone') }}</NativeSelectOption>
|
:model-value="atxConfig.power.device"
|
||||||
<NativeSelectOption v-for="dev in atxDevices.gpio_chips" :key="dev" :value="dev">{{ dev }}</NativeSelectOption>
|
@update:model-value="value => atxConfig.power.device = value === EMPTY_SELECT_VALUE ? '' : String(value)"
|
||||||
</NativeSelect>
|
>
|
||||||
|
<SelectTrigger id="power-device" class="w-full"><SelectValue :placeholder="t('settings.atxDriverNone')" /></SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.atxDriverNone') }}</SelectItem>
|
||||||
|
<SelectItem v-for="dev in atxDevices.gpio_chips" :key="dev" :value="dev">{{ dev }}</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="power-pin">{{ t('settings.atxPin') }}</Label>
|
<Label for="power-pin">{{ t('settings.atxPin') }}</Label>
|
||||||
@@ -4113,10 +4163,16 @@ watch(isWindows, () => {
|
|||||||
<div class="grid gap-3 sm:grid-cols-3">
|
<div class="grid gap-3 sm:grid-cols-3">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="reset-device">{{ t('settings.atxGpioChip') }}</Label>
|
<Label for="reset-device">{{ t('settings.atxGpioChip') }}</Label>
|
||||||
<NativeSelect id="reset-device" v-model="atxConfig.reset.device" class="w-full">
|
<Select
|
||||||
<NativeSelectOption value="">{{ t('settings.atxDriverNone') }}</NativeSelectOption>
|
:model-value="atxConfig.reset.device"
|
||||||
<NativeSelectOption v-for="dev in atxDevices.gpio_chips" :key="dev" :value="dev">{{ dev }}</NativeSelectOption>
|
@update:model-value="value => atxConfig.reset.device = value === EMPTY_SELECT_VALUE ? '' : String(value)"
|
||||||
</NativeSelect>
|
>
|
||||||
|
<SelectTrigger id="reset-device" class="w-full"><SelectValue :placeholder="t('settings.atxDriverNone')" /></SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.atxDriverNone') }}</SelectItem>
|
||||||
|
<SelectItem v-for="dev in atxDevices.gpio_chips" :key="dev" :value="dev">{{ dev }}</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="reset-pin">{{ t('settings.atxPin') }}</Label>
|
<Label for="reset-pin">{{ t('settings.atxPin') }}</Label>
|
||||||
@@ -4137,10 +4193,16 @@ watch(isWindows, () => {
|
|||||||
<div class="grid gap-3 sm:grid-cols-3">
|
<div class="grid gap-3 sm:grid-cols-3">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="led-device">{{ t('settings.atxGpioChip') }}</Label>
|
<Label for="led-device">{{ t('settings.atxGpioChip') }}</Label>
|
||||||
<NativeSelect id="led-device" v-model="atxConfig.led.device" class="w-full">
|
<Select
|
||||||
<NativeSelectOption value="">{{ t('settings.atxDriverNone') }}</NativeSelectOption>
|
:model-value="atxConfig.led.device"
|
||||||
<NativeSelectOption v-for="dev in atxDevices.gpio_chips" :key="dev" :value="dev">{{ dev }}</NativeSelectOption>
|
@update:model-value="value => atxConfig.led.device = value === EMPTY_SELECT_VALUE ? '' : String(value)"
|
||||||
</NativeSelect>
|
>
|
||||||
|
<SelectTrigger id="led-device" class="w-full"><SelectValue :placeholder="t('settings.atxDriverNone')" /></SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.atxDriverNone') }}</SelectItem>
|
||||||
|
<SelectItem v-for="dev in atxDevices.gpio_chips" :key="dev" :value="dev">{{ dev }}</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="led-pin">{{ t('settings.atxPin') }}</Label>
|
<Label for="led-pin">{{ t('settings.atxPin') }}</Label>
|
||||||
@@ -4161,10 +4223,16 @@ watch(isWindows, () => {
|
|||||||
<div class="grid gap-3 sm:grid-cols-3">
|
<div class="grid gap-3 sm:grid-cols-3">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="hdd-device">{{ t('settings.atxGpioChip') }}</Label>
|
<Label for="hdd-device">{{ t('settings.atxGpioChip') }}</Label>
|
||||||
<NativeSelect id="hdd-device" v-model="atxConfig.hdd.device" class="w-full">
|
<Select
|
||||||
<NativeSelectOption value="">{{ t('settings.atxDriverNone') }}</NativeSelectOption>
|
:model-value="atxConfig.hdd.device"
|
||||||
<NativeSelectOption v-for="dev in atxDevices.gpio_chips" :key="dev" :value="dev">{{ dev }}</NativeSelectOption>
|
@update:model-value="value => atxConfig.hdd.device = value === EMPTY_SELECT_VALUE ? '' : String(value)"
|
||||||
</NativeSelect>
|
>
|
||||||
|
<SelectTrigger id="hdd-device" class="w-full"><SelectValue :placeholder="t('settings.atxDriverNone')" /></SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.atxDriverNone') }}</SelectItem>
|
||||||
|
<SelectItem v-for="dev in atxDevices.gpio_chips" :key="dev" :value="dev">{{ dev }}</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="hdd-pin">{{ t('settings.atxPin') }}</Label>
|
<Label for="hdd-pin">{{ t('settings.atxPin') }}</Label>
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import {
|
|||||||
import { Switch } from '@/components/ui/switch'
|
import { Switch } from '@/components/ui/switch'
|
||||||
import { Stepper, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/components/ui/stepper'
|
import { Stepper, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/components/ui/stepper'
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||||
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
|
|
||||||
import {
|
import {
|
||||||
Eye,
|
Eye,
|
||||||
EyeOff,
|
EyeOff,
|
||||||
@@ -75,6 +74,7 @@ const platform = ref<PlatformCapabilities | null>(null)
|
|||||||
const isWindows = computed(() => platform.value?.mode === 'windows')
|
const isWindows = computed(() => platform.value?.mode === 'windows')
|
||||||
const audioSupported = computed(() => platform.value?.audio.available ?? true)
|
const audioSupported = computed(() => platform.value?.audio.available ?? true)
|
||||||
const totalSteps = 4
|
const totalSteps = 4
|
||||||
|
const EMPTY_SELECT_VALUE = '__one-kvm-empty-select-value__'
|
||||||
|
|
||||||
const hidBackend = ref('ch9329')
|
const hidBackend = ref('ch9329')
|
||||||
const ch9329Port = ref('')
|
const ch9329Port = ref('')
|
||||||
@@ -698,12 +698,20 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
|||||||
</HoverCardContent>
|
</HoverCardContent>
|
||||||
</HoverCard>
|
</HoverCard>
|
||||||
</div>
|
</div>
|
||||||
<NativeSelect v-model="videoDevice" class="w-full">
|
<Select
|
||||||
<NativeSelectOption value="">{{ t('setup.selectVideoDevice') }}</NativeSelectOption>
|
:model-value="videoDevice"
|
||||||
<NativeSelectOption v-for="dev in devices.video" :key="dev.path" :value="dev.path">
|
@update:model-value="value => videoDevice = value === EMPTY_SELECT_VALUE ? '' : String(value)"
|
||||||
|
>
|
||||||
|
<SelectTrigger id="videoDevice" class="w-full">
|
||||||
|
<SelectValue :placeholder="t('setup.selectVideoDevice')" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('setup.selectVideoDevice') }}</SelectItem>
|
||||||
|
<SelectItem v-for="dev in devices.video" :key="dev.path" :value="dev.path">
|
||||||
{{ formatVideoDeviceLabel(dev) }}
|
{{ formatVideoDeviceLabel(dev) }}
|
||||||
</NativeSelectOption>
|
</SelectItem>
|
||||||
</NativeSelect>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Alert v-if="videoDevice && !selectedDeviceHasSignal" variant="warning">
|
<Alert v-if="videoDevice && !selectedDeviceHasSignal" variant="warning">
|
||||||
@@ -731,37 +739,58 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
|||||||
</HoverCardContent>
|
</HoverCardContent>
|
||||||
</HoverCard>
|
</HoverCard>
|
||||||
</div>
|
</div>
|
||||||
<NativeSelect v-model="videoFormat" class="w-full">
|
<Select
|
||||||
<NativeSelectOption value="">{{ t('setup.selectFormat') }}</NativeSelectOption>
|
:model-value="videoFormat"
|
||||||
<NativeSelectOption v-for="fmt in availableFormats" :key="fmt.format" :value="fmt.format">
|
@update:model-value="value => videoFormat = value === EMPTY_SELECT_VALUE ? '' : String(value)"
|
||||||
|
>
|
||||||
|
<SelectTrigger id="videoFormat" class="w-full">
|
||||||
|
<SelectValue :placeholder="t('setup.selectFormat')" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('setup.selectFormat') }}</SelectItem>
|
||||||
|
<SelectItem v-for="fmt in availableFormats" :key="fmt.format" :value="fmt.format">
|
||||||
{{ fmt.format }} - {{ fmt.description }}
|
{{ fmt.format }} - {{ fmt.description }}
|
||||||
</NativeSelectOption>
|
</SelectItem>
|
||||||
</NativeSelect>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="videoFormat" class="grid grid-cols-2 gap-4">
|
<div v-if="videoFormat" class="grid grid-cols-2 gap-4">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="videoResolution">{{ t('setup.resolution') }}</Label>
|
<Label for="videoResolution">{{ t('setup.resolution') }}</Label>
|
||||||
<NativeSelect v-model="videoResolution" class="w-full">
|
<Select
|
||||||
<NativeSelectOption value="">{{ t('setup.selectResolution') }}</NativeSelectOption>
|
:model-value="videoResolution"
|
||||||
<NativeSelectOption
|
@update:model-value="value => videoResolution = value === EMPTY_SELECT_VALUE ? '' : String(value)"
|
||||||
|
>
|
||||||
|
<SelectTrigger id="videoResolution" class="w-full">
|
||||||
|
<SelectValue :placeholder="t('setup.selectResolution')" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('setup.selectResolution') }}</SelectItem>
|
||||||
|
<SelectItem
|
||||||
v-for="res in availableResolutions"
|
v-for="res in availableResolutions"
|
||||||
:key="`${res.width}x${res.height}`"
|
:key="`${res.width}x${res.height}`"
|
||||||
:value="`${res.width}x${res.height}`"
|
:value="`${res.width}x${res.height}`"
|
||||||
>
|
>
|
||||||
{{ res.width }}x{{ res.height }}
|
{{ res.width }}x{{ res.height }}
|
||||||
</NativeSelectOption>
|
</SelectItem>
|
||||||
</NativeSelect>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="videoFps">{{ t('setup.fps') }}</Label>
|
<Label for="videoFps">{{ t('setup.fps') }}</Label>
|
||||||
<NativeSelect :model-value="videoFps" class="w-full" @update:model-value="value => videoFps = value === '' ? null : Number(value)">
|
<Select :model-value="videoFps" @update:model-value="value => videoFps = value === EMPTY_SELECT_VALUE ? null : Number(value)">
|
||||||
<NativeSelectOption value="">{{ t('setup.selectFps') }}</NativeSelectOption>
|
<SelectTrigger id="videoFps" class="w-full">
|
||||||
<NativeSelectOption v-for="fps in availableFps" :key="fps" :value="fps">
|
<SelectValue :placeholder="t('setup.selectFps')" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('setup.selectFps') }}</SelectItem>
|
||||||
|
<SelectItem v-for="fps in availableFps" :key="fps" :value="fps">
|
||||||
{{ formatFpsLabel(fps) }}
|
{{ formatFpsLabel(fps) }}
|
||||||
</NativeSelectOption>
|
</SelectItem>
|
||||||
</NativeSelect>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -784,11 +813,20 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
|||||||
</HoverCardContent>
|
</HoverCardContent>
|
||||||
</HoverCard>
|
</HoverCard>
|
||||||
</div>
|
</div>
|
||||||
<NativeSelect v-model="audioDevice" class="w-full" :disabled="!audioEnabled">
|
<Select
|
||||||
<NativeSelectOption value="">{{ t('setup.selectAudioDevice') }}</NativeSelectOption>
|
:model-value="audioDevice"
|
||||||
<NativeSelectOption value="__none__">{{ t('setup.noAudio') }}</NativeSelectOption>
|
:disabled="!audioEnabled"
|
||||||
<NativeSelectOption v-for="dev in devices.audio" :key="dev.name" :value="dev.name">{{ dev.description }}{{ dev.is_hdmi ? ' (HDMI)' : '' }}</NativeSelectOption>
|
@update:model-value="value => audioDevice = value === EMPTY_SELECT_VALUE ? '' : String(value)"
|
||||||
</NativeSelect>
|
>
|
||||||
|
<SelectTrigger id="audioDevice" class="w-full">
|
||||||
|
<SelectValue :placeholder="t('setup.selectAudioDevice')" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('setup.selectAudioDevice') }}</SelectItem>
|
||||||
|
<SelectItem value="__none__">{{ t('setup.noAudio') }}</SelectItem>
|
||||||
|
<SelectItem v-for="dev in devices.audio" :key="dev.name" :value="dev.name">{{ dev.description }}{{ dev.is_hdmi ? ' (HDMI)' : '' }}</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
<p v-if="!devices.audio.length" class="text-xs text-muted-foreground">
|
<p v-if="!devices.audio.length" class="text-xs text-muted-foreground">
|
||||||
{{ t('setup.noAudioDevices') }}
|
{{ t('setup.noAudioDevices') }}
|
||||||
</p>
|
</p>
|
||||||
@@ -859,12 +897,20 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
|||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="ch9329Port">{{ t('setup.serialPort') }}</Label>
|
<Label for="ch9329Port">{{ t('setup.serialPort') }}</Label>
|
||||||
<NativeSelect v-model="ch9329Port" class="w-full">
|
<Select
|
||||||
<NativeSelectOption value="">{{ t('setup.selectSerialPort') }}</NativeSelectOption>
|
:model-value="ch9329Port"
|
||||||
<NativeSelectOption v-for="port in devices.serial" :key="port.path" :value="port.path">
|
@update:model-value="value => ch9329Port = value === EMPTY_SELECT_VALUE ? '' : String(value)"
|
||||||
|
>
|
||||||
|
<SelectTrigger id="ch9329Port" class="w-full">
|
||||||
|
<SelectValue :placeholder="t('setup.selectSerialPort')" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('setup.selectSerialPort') }}</SelectItem>
|
||||||
|
<SelectItem v-for="port in devices.serial" :key="port.path" :value="port.path">
|
||||||
{{ port.name }} ({{ port.path }})
|
{{ port.name }} ({{ port.path }})
|
||||||
</NativeSelectOption>
|
</SelectItem>
|
||||||
</NativeSelect>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
<p v-if="!devices.serial.length" class="text-xs text-muted-foreground">
|
<p v-if="!devices.serial.length" class="text-xs text-muted-foreground">
|
||||||
{{ t('setup.noSerialDevices') }}
|
{{ t('setup.noSerialDevices') }}
|
||||||
</p>
|
</p>
|
||||||
@@ -894,12 +940,20 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
|||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="otgUdc">{{ t('setup.udc') }}</Label>
|
<Label for="otgUdc">{{ t('setup.udc') }}</Label>
|
||||||
<NativeSelect v-model="otgUdc" class="w-full">
|
<Select
|
||||||
<NativeSelectOption value="">{{ t('setup.selectUdc') }}</NativeSelectOption>
|
:model-value="otgUdc"
|
||||||
<NativeSelectOption v-for="udc in devices.udc" :key="udc.name" :value="udc.name">
|
@update:model-value="value => otgUdc = value === EMPTY_SELECT_VALUE ? '' : String(value)"
|
||||||
|
>
|
||||||
|
<SelectTrigger id="otgUdc" class="w-full">
|
||||||
|
<SelectValue :placeholder="t('setup.selectUdc')" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('setup.selectUdc') }}</SelectItem>
|
||||||
|
<SelectItem v-for="udc in devices.udc" :key="udc.name" :value="udc.name">
|
||||||
{{ udc.name }}
|
{{ udc.name }}
|
||||||
</NativeSelectOption>
|
</SelectItem>
|
||||||
</NativeSelect>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
<p v-if="!devices.udc.length" class="text-xs text-muted-foreground">
|
<p v-if="!devices.udc.length" class="text-xs text-muted-foreground">
|
||||||
{{ t('setup.noUdcDevices') }}
|
{{ t('setup.noUdcDevices') }}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user