Files
ESH-Media/src/pages/HomePage.tsx
eshmeshek 2857a40d1e feat: trusted-domains OAuth popups, og:image bookmark posters, periodic updater
- main.js: trusted-domains list with default Google/Yandex/GitHub/etc.;
  cross-domain confirmation skipped for trusted; setWindowOpenHandler
  returns action:'allow' for trusted so OAuth popups work (postMessage
  back to opener, popup self-closes). Fixes YouTube/Google login reset.
- main.js: get-page-meta IPC extracts og:image / twitter:image / JSON-LD
  image from current view; HDRezka also tries .b-sidecover img for hi-res.
- Header: bookmark button pulls og:image as poster and the page's title;
  duplicate detection switched from hostname to full URL so multiple
  movies from same site can coexist.
- BookmarksBar: site icon rendered next to source domain when distinct
  from poster; img onerror falls back to placeholder.
- Settings: trusted domains chip list with add/remove/reset.
- Updater: proper semver compare (only show if latest > current),
  direct installer URL detection per platform, hourly re-check.

Bookmark schema gains optional siteIcon; existing bookmarks remain valid.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:56:12 +03:00

125 lines
4.8 KiB
TypeScript

import React, { useState, useEffect, useRef } from 'react'
import Header from '../components/Header'
import Sidebar from '../components/Sidebar'
import AppList from '../components/AppList'
import MovieSearch from '../components/MovieSearch'
import '../styles/main.css'
import { AppEntry, Bookmark } from '../components/Settings'
interface OpenedApp {
name: string
imageUrl: string
url: string
}
const HomePage: React.FC = () => {
const [openedApps, setOpenedApps] = useState<OpenedApp[]>([])
const [activeApp, setActiveApp] = useState<string>('home')
const [appCardList, setAppCardList] = useState<AppEntry[]>([])
const [movieQuery, setMovieQuery] = useState<string | null>(null)
const [movieSearchKey, setMovieSearchKey] = useState(0)
const [openedFromSearch, setOpenedFromSearch] = useState(false)
const [bookmarks, setBookmarks] = useState<Bookmark[]>([])
const configRef = useRef<any>({})
useEffect(() => {
if (!window.electron) return
window.electron.readConfig().then((cfg: any) => {
configRef.current = cfg ?? {}
if (cfg?.apps) setAppCardList(cfg.apps)
if (cfg?.bookmarks) setBookmarks(cfg.bookmarks)
if (cfg?.proxy?.host && cfg?.proxy?.port) {
window.electron!.setProxy(cfg.proxy.host, cfg.proxy.port)
}
})
const offOpenedApps = window.electron.on('update-opened-apps', (apps: OpenedApp[], activeName: string) => {
setOpenedApps(apps)
setActiveApp(activeName ?? 'home')
})
const offAlert = window.electron.on('alert', (text: string) => alert(text))
return () => { offOpenedApps(); offAlert() }
}, [])
const handleSidebarAppClick = (name: string) => {
setActiveApp(name)
window.electron?.showView(name)
}
const handleMovieSearch = (query: string) => {
window.electron?.hideView()
setMovieQuery(query)
setMovieSearchKey(k => k + 1)
setOpenedFromSearch(false)
setActiveApp('movie-search')
}
const handleMovieSearchOpen = (name: string, url: string) => {
window.electron?.createView(name, url, '', 1.0, resolveUseProxy(url))
setOpenedFromSearch(true)
setActiveApp(name)
}
const handleBackToSearch = () => {
window.electron?.hideView()
setActiveApp('movie-search')
}
const handleBookmarkAdd = (title: string, url: string, poster: string, source: string, siteIcon?: string) => {
// If caller didn't pass a site icon (e.g. movie search), look it up from the apps config by host.
let icon = siteIcon || ''
if (!icon) {
try {
const host = new URL(url).hostname
const match = appCardList.find(a => { try { return new URL(a.url).hostname === host } catch { return false } })
if (match?.imageUrl) icon = match.imageUrl
} catch {}
}
const sourceStr = source || (() => { try { return new URL(url).hostname.replace(/^www\./, '') } catch { return '' } })()
const updated = [...bookmarks, { title, url, poster, source: sourceStr, siteIcon: icon }]
setBookmarks(updated)
configRef.current = { ...configRef.current, bookmarks: updated }
window.electron?.writeConfig(configRef.current)
}
const handleBookmarkRemove = (index: number) => {
const updated = bookmarks.filter((_, i) => i !== index)
setBookmarks(updated)
configRef.current = { ...configRef.current, bookmarks: updated }
window.electron?.writeConfig(configRef.current)
}
const resolveUseProxy = (url: string) => {
try {
const host = new URL(url).hostname
const match = appCardList.find(a => { try { return new URL(a.url).hostname === host } catch { return false } })
return match ? match.useProxy : true
} catch { return true }
}
const handleBookmarkOpen = (b: Bookmark) => {
window.electron?.createView(b.title, b.url, b.poster || '', 1.0, resolveUseProxy(b.url))
setActiveApp(b.title)
}
const sidebarApps = openedApps.map(app => ({
...app,
isActive: activeApp === app.name,
onClick: () => handleSidebarAppClick(app.name),
}))
return (
<>
<Header activeApp={activeApp} setActiveApp={setActiveApp} onAppsChange={setAppCardList} onMovieSearch={handleMovieSearch} onBookmark={handleBookmarkAdd} onBookmarkRemove={handleBookmarkRemove} bookmarks={bookmarks} openedFromSearch={openedFromSearch} onBackToSearch={handleBackToSearch} />
<Sidebar openedApps={sidebarApps} activeApp={activeApp} setActiveApp={setActiveApp} />
<div style={{ display: activeApp === 'movie-search' ? undefined : 'none' }}>
<MovieSearch key={movieSearchKey} initialQuery={movieQuery ?? ''} onOpenUrl={handleMovieSearchOpen} onBookmark={handleBookmarkAdd} />
</div>
{activeApp !== 'movie-search' && (
<AppList apps={appCardList} bookmarks={bookmarks} onBookmarkOpen={handleBookmarkOpen} onBookmarkRemove={handleBookmarkRemove} />
)}
</>
)
}
export default HomePage