feat(1.0.13): тематики, стриминг поиска, авто-логотипы

- Тематики: 14 курированных категорий (Зомби, Космос, Вампиры,
  Постапокалипсис, Шпионы и т.д.) поверх жанров TMDB. discover-tmdb
  принимает themeKeywords (pipe-OR по with_keywords); для musical/
  superhero/martial добавлен extraGenre AND-связкой в movie-режиме.
- Стриминг поиска по сайтам: search-movies переведён с invoke→return
  на event-emit — карточки появляются по мере ответа каждого сайта,
  не ждут самого медленного. Спиннер виден до первого результата.
- Авто-детект логотипа сайта: поле "URL иконки" убрано из формы.
  Бэк IPC detect-logo пробует manifest.json → apple-touch-icon →
  link rel=icon ≥48px → JSON-LD → og:image → msapplication-TileImage
  → /favicon.ico (с проверкой content-type=image/*). Легаси-приложения
  без иконки догоняются тихо при открытии Settings.
This commit is contained in:
2026-05-17 11:15:42 +03:00
parent 1030622e19
commit 8684eb7b67
7 changed files with 338 additions and 60 deletions

View File

@@ -66,9 +66,33 @@ const Settings: React.FC<SettingsProps> = ({ onClose, onAppsChange }) => {
const [settings, setSettings] = useState<SettingsData>(DEFAULT_SETTINGS)
const [newApp, setNewApp] = useState<AppEntry>({ name: '', imageUrl: '', url: '', useProxy: false })
// Тихая авто-детекция логотипа: обновляет imageUrl в state+config когда сервер вернёт URL.
// Если детект ничего не нашёл — оставляем пустой imageUrl (тогда отобразится буква-заглушка).
const autoDetectLogo = (appUrl: string, onDetected: (logoUrl: string) => void) => {
if (!window.electron?.detectLogo) return
window.electron.detectLogo(appUrl).then(logoUrl => {
if (logoUrl) onDetected(logoUrl)
}).catch(() => {})
}
useEffect(() => {
window.electron?.readConfig().then((cfg: SettingsData | null) => {
if (cfg?.apps) setSettings(cfg)
if (!cfg?.apps) return
setSettings(cfg)
// Догоняем легаси-приложения без иконки: тихо парсим и обновляем конфиг.
const missing = cfg.apps.filter(a => !a.imageUrl && a.url)
if (!missing.length) return
missing.forEach(target => {
autoDetectLogo(target.url, logoUrl => {
setSettings(prev => {
const apps = prev.apps.map(a => a.url === target.url && !a.imageUrl ? { ...a, imageUrl: logoUrl } : a)
const updated = { ...prev, apps }
saveSettings(updated)
onAppsChange(apps)
return updated
})
})
})
})
}, [])
@@ -91,12 +115,23 @@ const Settings: React.FC<SettingsProps> = ({ onClose, onAppsChange }) => {
const addApp = () => {
if (!newApp.name || !newApp.url) return
const apps = [...settings.apps, newApp]
const fresh: AppEntry = { ...newApp, imageUrl: '' }
const apps = [...settings.apps, fresh]
const updated = { ...settings, apps }
setSettings(updated)
saveSettings(updated)
onAppsChange(apps)
setNewApp({ name: '', imageUrl: '', url: '', useProxy: false })
// Тихо детектим логотип в фоне; UI получит иконку, когда сервер ответит.
autoDetectLogo(fresh.url, logoUrl => {
setSettings(prev => {
const nextApps = prev.apps.map(a => a.url === fresh.url && !a.imageUrl ? { ...a, imageUrl: logoUrl } : a)
const next = { ...prev, apps: nextApps }
saveSettings(next)
onAppsChange(nextApps)
return next
})
})
}
const removeApp = (index: number) => {
@@ -268,12 +303,6 @@ const Settings: React.FC<SettingsProps> = ({ onClose, onAppsChange }) => {
value={newApp.url}
onChange={e => setNewApp({ ...newApp, url: e.target.value })}
/>
<input
className="settings-input"
placeholder="URL иконки (необязательно)"
value={newApp.imageUrl}
onChange={e => setNewApp({ ...newApp, imageUrl: e.target.value })}
/>
<label className="proxy-switch-label add-proxy-label">
<span>Использовать прокси</span>
<div