From 334c7426c254f63d5615a7d32e9dc6ad3f4694c5 Mon Sep 17 00:00:00 2001 From: Nexmoe <16796652+nexmoe@users.noreply.github.com> Date: Mon, 10 Nov 2025 21:22:59 +0800 Subject: [PATCH] feat(sites): show site icons and update supported sites layout (#23) Modified SupportedSites.tsx: - Added ImageWithPlaceholder import and IPC service import to fetch site icons. - Implemented SiteIcon component that loads site icon via ipcServices.app.getSiteIcon and handles errors/cancellation. - Updated layout: adjusted grid to include lg:grid-cols-3 and restructured list items into anchor cards with icon, label, external link icon, and description with truncation. These changes are focused on UI improvements for the SupportedSites page and fetching site icons from the main process. --- src/main/ipc/services/app-service.ts | 20 +++++++ src/renderer/src/data/popularSites.ts | 38 +++++++------- src/renderer/src/pages/SupportedSites.tsx | 63 ++++++++++++++++++++--- 3 files changed, 97 insertions(+), 24 deletions(-) diff --git a/src/main/ipc/services/app-service.ts b/src/main/ipc/services/app-service.ts index 8b9e258..5325737 100644 --- a/src/main/ipc/services/app-service.ts +++ b/src/main/ipc/services/app-service.ts @@ -32,6 +32,26 @@ class AppService extends IpcService { return dialog.showMessageBox(options) } + + @IpcMethod() + async getSiteIcon(_context: IpcContext, domain: string): Promise { + try { + const iconUrl = `https://unavatar.io/${domain}` + const response = await fetch(iconUrl) + if (!response.ok) { + return null + } + + const arrayBuffer = await response.arrayBuffer() + const buffer = Buffer.from(arrayBuffer) + const contentType = response.headers.get('content-type') || 'image/png' + const base64 = buffer.toString('base64') + return `data:${contentType};base64,${base64}` + } catch (error) { + console.error('Failed to fetch site icon:', error) + return null + } + } } export { AppService } diff --git a/src/renderer/src/data/popularSites.ts b/src/renderer/src/data/popularSites.ts index c99284f..34714e9 100644 --- a/src/renderer/src/data/popularSites.ts +++ b/src/renderer/src/data/popularSites.ts @@ -1,24 +1,26 @@ export interface PopularSite { id: string + url: string + domain: string } export const popularSites: PopularSite[] = [ - { id: 'youtube' }, - { id: 'youtubemusic' }, - { id: 'tiktok' }, - { id: 'facebook' }, - { id: 'instagram' }, - { id: 'twitter' }, - { id: 'soundcloud' }, - { id: 'reddit' }, - { id: 'vimeo' }, - { id: 'dailymotion' }, - { id: 'twitch' }, - { id: 'linkedin' }, - { id: 'pinterest' }, - { id: 'tumblr' }, - { id: 'mixcloud' }, - { id: 'niconico' }, - { id: 'kick' }, - { id: 'bandcamp' } + { id: 'youtube', url: 'https://www.youtube.com', domain: 'youtube.com' }, + { id: 'youtubemusic', url: 'https://music.youtube.com', domain: 'youtube.com' }, + { id: 'tiktok', url: 'https://www.tiktok.com', domain: 'tiktok.com' }, + { id: 'facebook', url: 'https://www.facebook.com', domain: 'facebook.com' }, + { id: 'instagram', url: 'https://www.instagram.com', domain: 'instagram.com' }, + { id: 'twitter', url: 'https://twitter.com', domain: 'twitter.com' }, + { id: 'soundcloud', url: 'https://soundcloud.com', domain: 'soundcloud.com' }, + { id: 'reddit', url: 'https://www.reddit.com', domain: 'reddit.com' }, + { id: 'vimeo', url: 'https://vimeo.com', domain: 'vimeo.com' }, + { id: 'dailymotion', url: 'https://www.dailymotion.com', domain: 'dailymotion.com' }, + { id: 'twitch', url: 'https://www.twitch.tv', domain: 'twitch.tv' }, + { id: 'linkedin', url: 'https://www.linkedin.com', domain: 'linkedin.com' }, + { id: 'pinterest', url: 'https://www.pinterest.com', domain: 'pinterest.com' }, + { id: 'tumblr', url: 'https://www.tumblr.com', domain: 'tumblr.com' }, + { id: 'mixcloud', url: 'https://www.mixcloud.com', domain: 'mixcloud.com' }, + { id: 'niconico', url: 'https://www.nicovideo.jp', domain: 'nicovideo.jp' }, + { id: 'kick', url: 'https://kick.com', domain: 'kick.com' }, + { id: 'bandcamp', url: 'https://bandcamp.com', domain: 'bandcamp.com' } ] diff --git a/src/renderer/src/pages/SupportedSites.tsx b/src/renderer/src/pages/SupportedSites.tsx index ff954b6..ed117a3 100644 --- a/src/renderer/src/pages/SupportedSites.tsx +++ b/src/renderer/src/pages/SupportedSites.tsx @@ -6,11 +6,45 @@ import { CardHeader, CardTitle } from '@renderer/components/ui/card' +import { ImageWithPlaceholder } from '@renderer/components/ui/image-with-placeholder' 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(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 +} + export function SupportedSites() { const { t } = useTranslation() @@ -28,7 +62,7 @@ export function SupportedSites() {

{t('sites.popularSection')}

-
    +
      {popularSites.map((site) => { const labelKey = `sites.popular.${site.id}.label` const descriptionKey = `sites.popular.${site.id}.description` @@ -37,11 +71,28 @@ export function SupportedSites() { const hasDescription = description !== descriptionKey return ( -
    • -

      {label}

      - {hasDescription ? ( -

      {description}

      - ) : null} +
    • + +
      + +
      +
      +
      +

      {label}

      + +
      + {hasDescription ? ( +

      + {description} +

      + ) : null} +
      +
    • ) })}