Remove supported sites page (#40)
* Remove supported sites page * Remove supported sites page component
This commit is contained in:
@@ -14,18 +14,16 @@ import { About } from './pages/About'
|
||||
import { Home } from './pages/Home'
|
||||
import { Settings } from './pages/Settings'
|
||||
import { Subscriptions } from './pages/Subscriptions'
|
||||
import { SupportedSites } from './pages/SupportedSites'
|
||||
import { loadSettingsAtom, settingsAtom } from './store/settings'
|
||||
import { loadSubscriptionsAtom, setSubscriptionsAtom } from './store/subscriptions'
|
||||
|
||||
type Page = 'home' | 'subscriptions' | 'settings' | 'about' | 'sites'
|
||||
type Page = 'home' | 'subscriptions' | 'settings' | 'about'
|
||||
|
||||
const pageToPath: Record<Page, string> = {
|
||||
home: '/',
|
||||
subscriptions: '/subscriptions',
|
||||
settings: '/settings',
|
||||
about: '/about',
|
||||
sites: '/sites'
|
||||
about: '/about'
|
||||
}
|
||||
|
||||
const normalizePathname = (pathname: string): string => {
|
||||
@@ -42,8 +40,6 @@ const pathToPage = (pathname: string): Page => {
|
||||
return 'settings'
|
||||
case '/about':
|
||||
return 'about'
|
||||
case '/sites':
|
||||
return 'sites'
|
||||
default:
|
||||
return 'home'
|
||||
}
|
||||
@@ -61,6 +57,7 @@ function AppContent() {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const currentPage = pathToPage(location.pathname)
|
||||
const supportedSitesUrl = 'https://vidbee.org/supported-sites/'
|
||||
|
||||
const handlePageChange = (page: Page) => {
|
||||
const targetPath = pageToPath[page] ?? '/'
|
||||
@@ -69,6 +66,10 @@ function AppContent() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleOpenSupportedSites = () => {
|
||||
window.open(supportedSitesUrl, '_blank')
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadSettings()
|
||||
}, [loadSettings])
|
||||
@@ -227,7 +228,11 @@ function AppContent() {
|
||||
return (
|
||||
<div className="flex flex-row h-screen">
|
||||
{/* Sidebar Navigation */}
|
||||
<Sidebar currentPage={currentPage} onPageChange={handlePageChange} />
|
||||
<Sidebar
|
||||
currentPage={currentPage}
|
||||
onPageChange={handlePageChange}
|
||||
onOpenSupportedSites={handleOpenSupportedSites}
|
||||
/>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex flex-col flex-1 min-h-0 overflow-hidden bg-background">
|
||||
@@ -244,7 +249,7 @@ function AppContent() {
|
||||
path="/"
|
||||
element={
|
||||
<Home
|
||||
onOpenSupportedSites={() => handlePageChange('sites')}
|
||||
onOpenSupportedSites={handleOpenSupportedSites}
|
||||
onOpenSettings={() => handlePageChange('settings')}
|
||||
/>
|
||||
}
|
||||
@@ -252,7 +257,6 @@ function AppContent() {
|
||||
<Route path="/subscriptions" element={<Subscriptions />} />
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="/sites" element={<SupportedSites />} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</div>
|
||||
|
||||
@@ -24,9 +24,20 @@ import MingcuteRssLine from '~icons/mingcute/rss-line'
|
||||
import MingcuteSettingsFill from '~icons/mingcute/settings-3-fill'
|
||||
import MingcuteSettingsLine from '~icons/mingcute/settings-3-line'
|
||||
|
||||
type Page = 'home' | 'subscriptions' | 'settings' | 'about' | 'sites'
|
||||
type Page = 'home' | 'subscriptions' | 'settings' | 'about'
|
||||
type NavigationTarget = Page | 'supported-sites'
|
||||
|
||||
interface NavigationItem {
|
||||
id: NavigationTarget
|
||||
icon: {
|
||||
active: React.ComponentType<{ className?: string }>
|
||||
inactive: React.ComponentType<{ className?: string }>
|
||||
}
|
||||
label: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
interface PageNavigationItem {
|
||||
id: Page
|
||||
icon: {
|
||||
active: React.ComponentType<{ className?: string }>
|
||||
@@ -38,9 +49,10 @@ interface NavigationItem {
|
||||
interface SidebarProps {
|
||||
currentPage: Page
|
||||
onPageChange: (page: Page) => void
|
||||
onOpenSupportedSites: () => void
|
||||
}
|
||||
|
||||
export function Sidebar({ currentPage, onPageChange }: SidebarProps) {
|
||||
export function Sidebar({ currentPage, onPageChange, onOpenSupportedSites }: SidebarProps) {
|
||||
const { t, i18n } = useTranslation()
|
||||
const saveSetting = useSetAtom(saveSettingAtom)
|
||||
|
||||
@@ -64,16 +76,17 @@ export function Sidebar({ currentPage, onPageChange }: SidebarProps) {
|
||||
label: t('menu.rss')
|
||||
},
|
||||
{
|
||||
id: 'sites',
|
||||
id: 'supported-sites',
|
||||
icon: {
|
||||
active: MingcuteCheckCircleFill,
|
||||
inactive: MingcuteCheckCircleLine
|
||||
},
|
||||
label: t('menu.supportedSites')
|
||||
label: t('menu.supportedSites'),
|
||||
onClick: onOpenSupportedSites
|
||||
}
|
||||
]
|
||||
|
||||
const bottomNavigationItems: NavigationItem[] = [
|
||||
const bottomNavigationItems: PageNavigationItem[] = [
|
||||
{
|
||||
id: 'settings',
|
||||
icon: {
|
||||
@@ -107,15 +120,16 @@ export function Sidebar({ currentPage, onPageChange }: SidebarProps) {
|
||||
}
|
||||
|
||||
const renderNavigationItem = (item: NavigationItem, showLabel = true) => {
|
||||
const isActive = currentPage === item.id
|
||||
const isActive = item.id !== 'supported-sites' && currentPage === item.id
|
||||
const IconComponent = isActive ? item.icon.active : item.icon.inactive
|
||||
const handleClick = item.onClick ?? (() => onPageChange(item.id as Page))
|
||||
|
||||
return (
|
||||
<div key={item.id} className="flex flex-col items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onPageChange(item.id)}
|
||||
onClick={handleClick}
|
||||
className={`no-drag w-12 h-12 ${isActive ? 'bg-primary/10' : ''}`}
|
||||
>
|
||||
<IconComponent className={`h-5! w-5! ${isActive ? 'text-primary' : ''}`} />
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
import { Button } from '@renderer/components/ui/button'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '@renderer/components/ui/card'
|
||||
import { RemoteImage } from '@renderer/components/ui/remote-image'
|
||||
import { popularSites } from '@renderer/data/popularSites'
|
||||
import { ipcServices } from '@renderer/lib/ipc'
|
||||
import { ExternalLink } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const referenceUrl = 'https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md'
|
||||
|
||||
function SiteIcon({ domain, alt }: { domain: string; alt: string }) {
|
||||
const [iconUrl, setIconUrl] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
|
||||
const loadIcon = async () => {
|
||||
try {
|
||||
const dataUrl = await ipcServices.app.getSiteIcon(domain)
|
||||
if (!cancelled) {
|
||||
setIconUrl(dataUrl)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load site icon:', error)
|
||||
if (!cancelled) {
|
||||
setIconUrl(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadIcon()
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [domain])
|
||||
|
||||
return <RemoteImage src={iconUrl} alt={alt} className="w-full h-full" />
|
||||
}
|
||||
|
||||
export function SupportedSites() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="container mx-auto max-w-5xl p-6 space-y-6" style={{ maxWidth: '100%' }}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('sites.pageTitle')}</CardTitle>
|
||||
<CardDescription>{t('sites.pageDescription')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">{t('sites.pageIntro')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<section className="space-y-3">
|
||||
<h2 className="text-lg font-semibold px-6">{t('sites.popularSection')}</h2>
|
||||
<ul className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{popularSites.map((site) => {
|
||||
const labelKey = `sites.popular.${site.id}.label`
|
||||
const descriptionKey = `sites.popular.${site.id}.description`
|
||||
const label = t(labelKey)
|
||||
const description = t(descriptionKey)
|
||||
const hasDescription = description !== descriptionKey
|
||||
|
||||
return (
|
||||
<li key={site.id}>
|
||||
<a
|
||||
href={site.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="flex items-center gap-3 rounded-md border border-border px-4 py-3 transition-colors hover:bg-muted/50 hover:border-primary/50 group"
|
||||
>
|
||||
<div className="shrink-0 w-10 h-10 rounded-xs overflow-hidden">
|
||||
<SiteIcon domain={site.domain} alt={label} />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-sm font-medium truncate">{label}</p>
|
||||
<ExternalLink className="w-3 h-3 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity shrink-0" />
|
||||
</div>
|
||||
{hasDescription ? (
|
||||
<p className="mt-0.5 text-xs text-muted-foreground line-clamp-1">
|
||||
{description}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('sites.moreTitle')}</CardTitle>
|
||||
<CardDescription>{t('sites.moreDescription')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button asChild variant="outline">
|
||||
<a href={referenceUrl} target="_blank" rel="noreferrer">
|
||||
{t('sites.openFullList')}
|
||||
</a>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user