Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e22fe7679a | ||
|
|
7c2e526c49 | ||
|
|
2f5776d1e0 | ||
|
|
7901fc7e82 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vidbee",
|
||||
"version": "0.3.4",
|
||||
"version": "0.3.5",
|
||||
"description": "A modern Electron application for downloading videos and audios",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "VidBee",
|
||||
|
||||
@@ -6,7 +6,6 @@ import type {
|
||||
} from '../../shared/types'
|
||||
|
||||
const qualityPresetToVideoHeight: Record<OneClickQualityPreset, number | null> = {
|
||||
auto: null,
|
||||
best: null,
|
||||
good: 1080,
|
||||
normal: 720,
|
||||
@@ -15,7 +14,6 @@ const qualityPresetToVideoHeight: Record<OneClickQualityPreset, number | null> =
|
||||
}
|
||||
|
||||
const qualityPresetToAudioAbr: Record<OneClickQualityPreset, number | null> = {
|
||||
auto: null,
|
||||
best: 320,
|
||||
good: 256,
|
||||
normal: 192,
|
||||
@@ -187,7 +185,7 @@ export const resolveSelectedFormat = (
|
||||
return directMatch
|
||||
}
|
||||
|
||||
const preset = settings.oneClickQuality ?? 'auto'
|
||||
const preset = settings.oneClickQuality ?? 'best'
|
||||
|
||||
if (options.type === 'video') {
|
||||
const videoFormats = formats.filter(
|
||||
|
||||
@@ -229,6 +229,25 @@ class FileSystemService extends IpcService {
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
@IpcMethod()
|
||||
async fileExists(_context: IpcContext, filePath: string): Promise<boolean> {
|
||||
try {
|
||||
if (!filePath) {
|
||||
return false
|
||||
}
|
||||
|
||||
const sanitizedPath = this.sanitizePath(filePath)
|
||||
const normalizedPath = path.normalize(sanitizedPath)
|
||||
|
||||
const stats = await fs.stat(normalizedPath).catch(() => null)
|
||||
|
||||
return stats?.isFile() ?? false
|
||||
} catch (error) {
|
||||
console.error('Failed to check file existence:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@IpcMethod()
|
||||
async deleteFile(_context: IpcContext, filePath: string): Promise<boolean> {
|
||||
try {
|
||||
|
||||
@@ -153,7 +153,12 @@ function AppContent() {
|
||||
const renderPage = () => {
|
||||
switch (currentPage) {
|
||||
case 'home':
|
||||
return <Home onOpenSupportedSites={() => setCurrentPage('sites')} />
|
||||
return (
|
||||
<Home
|
||||
onOpenSupportedSites={() => setCurrentPage('sites')}
|
||||
onOpenSettings={() => setCurrentPage('settings')}
|
||||
/>
|
||||
)
|
||||
case 'settings':
|
||||
return <Settings />
|
||||
case 'about':
|
||||
@@ -161,7 +166,12 @@ function AppContent() {
|
||||
case 'sites':
|
||||
return <SupportedSites />
|
||||
default:
|
||||
return <Home onOpenSupportedSites={() => setCurrentPage('sites')} />
|
||||
return (
|
||||
<Home
|
||||
onOpenSupportedSites={() => setCurrentPage('sites')}
|
||||
onOpenSettings={() => setCurrentPage('settings')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,32 +60,32 @@
|
||||
--card-foreground: oklch(0.8853 0 0);
|
||||
--popover: oklch(0 0 0);
|
||||
--popover-foreground: oklch(0.9328 0.0025 228.7857);
|
||||
--primary: oklch(0.6692 0.1607 245.0110);
|
||||
--primary: oklch(0.8223 0.1704 79.8747);
|
||||
--primary-foreground: oklch(1.0000 0 0);
|
||||
--secondary: oklch(0.9622 0.0035 219.5331);
|
||||
--secondary-foreground: oklch(0.1884 0.0128 248.5103);
|
||||
--muted: oklch(0.2090 0 0);
|
||||
--muted: oklch(0.3485 0 0);
|
||||
--muted-foreground: oklch(0.5637 0.0078 247.9662);
|
||||
--accent: oklch(0.1928 0.0331 242.5459);
|
||||
--accent-foreground: oklch(0.6692 0.1607 245.0110);
|
||||
--accent-foreground: oklch(0.8223 0.1704 79.8747);
|
||||
--destructive: oklch(0.6188 0.2376 25.7658);
|
||||
--destructive-foreground: oklch(1.0000 0 0);
|
||||
--border: oklch(0.2674 0.0047 248.0045);
|
||||
--input: oklch(0.3020 0.0288 244.8244);
|
||||
--ring: oklch(0.6818 0.1584 243.3540);
|
||||
--chart-1: oklch(0.6723 0.1606 244.9955);
|
||||
--ring: oklch(0.8223 0.1704 79.8747);
|
||||
--chart-1: oklch(0.8223 0.1704 79.8747);
|
||||
--chart-2: oklch(0.6907 0.1554 160.3454);
|
||||
--chart-3: oklch(0.8214 0.1600 82.5337);
|
||||
--chart-4: oklch(0.7064 0.1822 151.7125);
|
||||
--chart-5: oklch(0.5919 0.2186 10.5826);
|
||||
--sidebar: oklch(0.2097 0.0080 274.5332);
|
||||
--sidebar-foreground: oklch(0.8853 0 0);
|
||||
--sidebar-primary: oklch(0.6818 0.1584 243.3540);
|
||||
--sidebar-primary: oklch(0.8223 0.1704 79.8747);
|
||||
--sidebar-primary-foreground: oklch(1.0000 0 0);
|
||||
--sidebar-accent: oklch(0.1928 0.0331 242.5459);
|
||||
--sidebar-accent-foreground: oklch(0.6692 0.1607 245.0110);
|
||||
--sidebar-accent-foreground: oklch(0.8223 0.1704 79.8747);
|
||||
--sidebar-border: oklch(0.3795 0.0220 240.5943);
|
||||
--sidebar-ring: oklch(0.6818 0.1584 243.3540);
|
||||
--sidebar-ring: oklch(0.8223 0.1704 79.8747);
|
||||
--font-sans: Open Sans, sans-serif;
|
||||
--font-serif: Georgia, serif;
|
||||
--font-mono: Menlo, monospace;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Progress } from '@renderer/components/ui/progress'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { AlertCircle, CheckCircle2, Copy, FolderOpen, Loader2, Play, Trash2, X } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import { useCachedThumbnail } from '../../hooks/use-cached-thumbnail'
|
||||
@@ -67,6 +68,30 @@ export function DownloadItem({ download }: DownloadItemProps) {
|
||||
? actionsContainerBaseClass
|
||||
: `${actionsContainerBaseClass} sm:opacity-0 sm:group-hover:opacity-100`
|
||||
|
||||
// Track if the file exists
|
||||
const [fileExists, setFileExists] = useState(false)
|
||||
|
||||
// Check if file exists when download data changes
|
||||
useEffect(() => {
|
||||
const checkFileExists = async () => {
|
||||
if (!download.title || !download.downloadPath || !download.format) {
|
||||
setFileExists(false)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const filePath = generateFilePath(download.downloadPath, download.title, download.format)
|
||||
const exists = await ipcServices.fs.fileExists(filePath)
|
||||
setFileExists(exists)
|
||||
} catch (error) {
|
||||
console.error('Failed to check file existence:', error)
|
||||
setFileExists(false)
|
||||
}
|
||||
}
|
||||
|
||||
checkFileExists()
|
||||
}, [download.title, download.downloadPath, download.format])
|
||||
|
||||
const handleCancel = async () => {
|
||||
if (isHistory) return
|
||||
try {
|
||||
@@ -93,18 +118,31 @@ export function DownloadItem({ download }: DownloadItemProps) {
|
||||
toast.error(t('notifications.openFolderFailed'))
|
||||
}
|
||||
}
|
||||
// Check if copy to clipboard is available
|
||||
const canCopyToClipboard = () => {
|
||||
return !!(download.title && download.downloadPath && download.format && fileExists)
|
||||
}
|
||||
|
||||
// need title, downloadPath, format
|
||||
const handleCopyToClipboard = async () => {
|
||||
if (!download.title || !download.downloadPath || !download.format) {
|
||||
if (!canCopyToClipboard()) {
|
||||
toast.error(t('notifications.copyFailed'))
|
||||
return
|
||||
}
|
||||
|
||||
// Type guard: these values are guaranteed to exist after canCopyToClipboard() check
|
||||
const downloadPath = download.downloadPath
|
||||
const format = download.format
|
||||
const title = download.title
|
||||
|
||||
if (!downloadPath || !format || !title) {
|
||||
toast.error(t('notifications.copyFailed'))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate file path using downloadPath + title + ext
|
||||
const downloadPath = download.downloadPath
|
||||
const format = download.format
|
||||
const filePath = generateFilePath(downloadPath, download.title, format)
|
||||
const filePath = generateFilePath(downloadPath, title, format)
|
||||
|
||||
const success = await ipcServices.fs.copyFileToClipboard(filePath)
|
||||
if (!success) {
|
||||
@@ -320,7 +358,7 @@ export function DownloadItem({ download }: DownloadItemProps) {
|
||||
size="icon"
|
||||
className="h-8 w-8 shrink-0"
|
||||
onClick={handleCopyToClipboard}
|
||||
disabled={!download.title || !download.downloadPath || !download.format}
|
||||
disabled={!canCopyToClipboard()}
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -373,6 +411,7 @@ export function DownloadItem({ download }: DownloadItemProps) {
|
||||
size="icon"
|
||||
className="h-8 w-8 shrink-0"
|
||||
onClick={handleCopyToClipboard}
|
||||
disabled={!canCopyToClipboard()}
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Button } from '@renderer/components/ui/button'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@renderer/components/ui/card'
|
||||
import { CardContent, CardHeader } from '@renderer/components/ui/card'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { History as HistoryIcon } from 'lucide-react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useHistorySync } from '../../hooks/use-history-sync'
|
||||
import type { DownloadRecord } from '../../store/downloads'
|
||||
import { clearCompletedAtom, downloadStatsAtom, downloadsArrayAtom } from '../../store/downloads'
|
||||
import { downloadStatsAtom, downloadsArrayAtom } from '../../store/downloads'
|
||||
import { DownloadItem } from './DownloadItem'
|
||||
import { PlaylistDownloadGroup } from './PlaylistDownloadGroup'
|
||||
|
||||
@@ -17,7 +17,6 @@ export function UnifiedDownloadHistory() {
|
||||
const { t } = useTranslation()
|
||||
const allRecords = useAtomValue(downloadsArrayAtom)
|
||||
const downloadStats = useAtomValue(downloadStatsAtom)
|
||||
const clearCompleted = useSetAtom(clearCompletedAtom)
|
||||
const [statusFilter, setStatusFilter] = useState<StatusFilter>('all')
|
||||
|
||||
useHistorySync()
|
||||
@@ -99,33 +98,9 @@ export function UnifiedDownloadHistory() {
|
||||
return { order, groups }
|
||||
}, [filteredRecords])
|
||||
|
||||
const hasCompletedActive = allRecords.some(
|
||||
(item) => item.entryType === 'active' && item.status === 'completed'
|
||||
)
|
||||
const handleClearCompleted = () => {
|
||||
clearCompleted()
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="border border-border/60 bg-background max-w-full shadow-sm backdrop-blur-sm">
|
||||
<CardHeader className="gap-4">
|
||||
<div className="flex flex-wrap items-center justify-between gap-4">
|
||||
<div className="space-y-1">
|
||||
<CardTitle>{t('download.downloadQueue')}</CardTitle>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2 text-xs">
|
||||
{hasCompletedActive && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 border border-border/60 px-3"
|
||||
onClick={handleClearCompleted}
|
||||
>
|
||||
{t('download.clearCompleted')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<CardHeader className="gap-4 p-0">
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm">
|
||||
{filters.map((filter) => {
|
||||
const isActive = statusFilter === filter.key
|
||||
@@ -155,7 +130,7 @@ export function UnifiedDownloadHistory() {
|
||||
})}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 overflow-hidden w-full">
|
||||
<CardContent className="space-y-3 p-0 overflow-hidden w-full">
|
||||
{filteredRecords.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center gap-3 rounded-lg border border-dashed border-border/60 px-6 py-10 text-center text-muted-foreground">
|
||||
<HistoryIcon className="h-10 w-10 opacity-50" />
|
||||
@@ -191,6 +166,6 @@ export function UnifiedDownloadHistory() {
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import { useTranslation } from 'react-i18next'
|
||||
import type { OneClickQualityPreset, VideoFormat } from '../../../../shared/types'
|
||||
|
||||
const qualityPresetToVideoHeight: Record<OneClickQualityPreset, number | null> = {
|
||||
auto: null,
|
||||
best: null,
|
||||
good: 1080,
|
||||
normal: 720,
|
||||
|
||||
@@ -139,8 +139,10 @@
|
||||
"noAudio": "No Audio",
|
||||
"noHistory": "No download history",
|
||||
"noItems": "No items found",
|
||||
"goToSettings": "Go to Settings",
|
||||
"oneClickDownload": "One-Click Download",
|
||||
"oneClickDownloadDescription": "Download directly with default settings without confirmation",
|
||||
"oneClickDownloadEnabled": "One-Click Download is enabled. Downloads will start directly with default settings.",
|
||||
"oneClickDownloadNow": "Download Now",
|
||||
"oneClickDownloadStarted": "Download started with default settings",
|
||||
"paste": "Paste",
|
||||
|
||||
@@ -319,49 +319,49 @@ export function About() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('about.followAuthorTitle')}</CardTitle>
|
||||
<CardDescription>{t('about.followAuthorDescription')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||
<p className="text-sm text-muted-foreground md:max-w-md">
|
||||
{t('about.followAuthorSupport')}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => openShareUrl('https://x.com/nexmoex')}
|
||||
className="gap-2"
|
||||
>
|
||||
<Twitter className="h-4 w-4" />
|
||||
{t('about.followAuthorActions.follow')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('about.shareTitle')}</CardTitle>
|
||||
<CardDescription>{t('about.shareDescription')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||
<p className="text-sm text-muted-foreground md:max-w-md">{t('about.shareSupport')}</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button variant="outline" size="sm" onClick={handleShareTwitter} className="gap-2">
|
||||
<Twitter className="h-4 w-4" />
|
||||
{t('about.shareActions.twitter')}
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={handleShareFacebook} className="gap-2">
|
||||
<Facebook className="h-4 w-4" />
|
||||
{t('about.shareActions.facebook')}
|
||||
</Button>
|
||||
<Button variant="secondary" size="sm" onClick={handleCopyShareLink} className="gap-2">
|
||||
<LinkIcon className="h-4 w-4" />
|
||||
{t('about.shareActions.copy')}
|
||||
</Button>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||
<p className="text-sm text-muted-foreground md:max-w-md">{t('about.shareSupport')}</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button variant="outline" size="sm" onClick={handleShareTwitter} className="gap-2">
|
||||
<Twitter className="h-4 w-4" />
|
||||
{t('about.shareActions.twitter')}
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={handleShareFacebook} className="gap-2">
|
||||
<Facebook className="h-4 w-4" />
|
||||
{t('about.shareActions.facebook')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={handleCopyShareLink}
|
||||
className="gap-2"
|
||||
>
|
||||
<LinkIcon className="h-4 w-4" />
|
||||
{t('about.shareActions.copy')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||
<p className="text-sm text-muted-foreground md:max-w-md">
|
||||
{t('about.followAuthorSupport')}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => openShareUrl('https://x.com/nexmoex')}
|
||||
className="gap-2"
|
||||
>
|
||||
<Twitter className="h-4 w-4" />
|
||||
{t('about.followAuthorActions.follow')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -19,7 +19,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/u
|
||||
import { popularSites } from '@renderer/data/popularSites'
|
||||
import type { AppSettings, OneClickQualityPreset, PlaylistInfo } from '@shared/types'
|
||||
import { useAtom, useSetAtom } from 'jotai'
|
||||
import { AlertCircle, Download, ListVideo, Loader2, Search } from 'lucide-react'
|
||||
import { AlertCircle, Download, Loader2, Search } from 'lucide-react'
|
||||
import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
@@ -42,7 +42,6 @@ import {
|
||||
} from '../store/video'
|
||||
|
||||
const qualityPresetToVideoHeight: Record<OneClickQualityPreset, number | null> = {
|
||||
auto: null,
|
||||
best: null,
|
||||
good: 1080,
|
||||
normal: 720,
|
||||
@@ -51,7 +50,6 @@ const qualityPresetToVideoHeight: Record<OneClickQualityPreset, number | null> =
|
||||
}
|
||||
|
||||
const qualityPresetToAudioAbr: Record<OneClickQualityPreset, number | null> = {
|
||||
auto: null,
|
||||
best: 320,
|
||||
good: 256,
|
||||
normal: 192,
|
||||
@@ -72,7 +70,7 @@ const dedupe = (candidates: Array<string | undefined>): string[] => {
|
||||
}
|
||||
|
||||
const getQualityPreset = (settings: AppSettings): OneClickQualityPreset =>
|
||||
settings.oneClickQuality ?? 'auto'
|
||||
settings.oneClickQuality ?? 'best'
|
||||
|
||||
const buildAudioSelectors = (preset: OneClickQualityPreset): string[] => {
|
||||
if (preset === 'worst') {
|
||||
@@ -126,9 +124,10 @@ const buildAudioFormatPreference = (settings: AppSettings): string => {
|
||||
|
||||
interface HomeProps {
|
||||
onOpenSupportedSites?: () => void
|
||||
onOpenSettings?: () => void
|
||||
}
|
||||
|
||||
export function Home({ onOpenSupportedSites }: HomeProps) {
|
||||
export function Home({ onOpenSupportedSites, onOpenSettings }: HomeProps) {
|
||||
const { t } = useTranslation()
|
||||
const [videoInfo, _setVideoInfo] = useAtom(currentVideoInfoAtom)
|
||||
const [loading] = useAtom(videoInfoLoadingAtom)
|
||||
@@ -540,11 +539,9 @@ export function Home({ onOpenSupportedSites }: HomeProps) {
|
||||
<Tabs defaultValue="single" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="single" className="flex items-center gap-2">
|
||||
<Download className="h-4 w-4" />
|
||||
{t('download.singleVideo')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="playlist" className="flex items-center gap-2">
|
||||
<ListVideo className="h-4 w-4" />
|
||||
{t('playlist.title')}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
@@ -624,18 +621,20 @@ export function Home({ onOpenSupportedSites }: HomeProps) {
|
||||
|
||||
{/* One-Click Download Info */}
|
||||
{settings.oneClickDownload && (
|
||||
<div className="rounded-lg bg-blue-50 dark:bg-blue-900/10 p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<Download className="h-5 w-5 text-blue-600 mt-0.5 dark:text-blue-400" />
|
||||
<div className="flex-1 space-y-1">
|
||||
<p className="text-sm font-medium text-blue-900 dark:text-blue-100">
|
||||
{t('download.oneClickDownload')}
|
||||
</p>
|
||||
<p className="text-sm text-blue-700">
|
||||
{t('download.oneClickDownloadDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2 rounded-lg bg-card px-4 py-3 text-sm text-muted-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{t('download.oneClickDownloadEnabled')}</span>
|
||||
</div>
|
||||
{onOpenSettings && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="link"
|
||||
className="h-auto px-2 py-0 text-xs"
|
||||
onClick={onOpenSettings}
|
||||
>
|
||||
{t('download.goToSettings')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -235,9 +235,6 @@ export function Settings() {
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="auto">
|
||||
{t('settings.oneClickQualityOptions.auto')}
|
||||
</SelectItem>
|
||||
<SelectItem value="best">
|
||||
{t('settings.oneClickQualityOptions.best')}
|
||||
</SelectItem>
|
||||
|
||||
@@ -172,7 +172,7 @@ export interface PlaylistDownloadResult {
|
||||
}
|
||||
|
||||
// Settings types
|
||||
export type OneClickQualityPreset = 'auto' | 'best' | 'good' | 'normal' | 'bad' | 'worst'
|
||||
export type OneClickQualityPreset = 'best' | 'good' | 'normal' | 'bad' | 'worst'
|
||||
|
||||
export interface AppSettings {
|
||||
downloadPath: string
|
||||
@@ -206,7 +206,7 @@ export const defaultSettings: AppSettings = {
|
||||
theme: 'system',
|
||||
oneClickDownload: false,
|
||||
oneClickDownloadType: 'video',
|
||||
oneClickQuality: 'auto',
|
||||
oneClickQuality: 'best',
|
||||
closeToTray: false,
|
||||
hideDockIcon: false,
|
||||
autoUpdate: true
|
||||
|
||||
Reference in New Issue
Block a user