Electron-based kiosk desktop app: large-tile launcher for YouTube, RuTube, movie sites and Google services, designed for low-tech grandparent use. Features: - WebContentsView-per-app tabbed browsing with session persistence - per-app proxy routing (Clash/V2Ray friendly, useProxy flag) - cliqz-electron adblocker with whitelist for OAuth/integrity domains - TMDB-backed movie search across kinogo / hdrezka / filmix - bookmark posters auto-fetched from og:image / JSON-LD - electron-updater wired to Gitea releases API (latest.yml + .blockmap) - cross-domain navigation confirms via custom WebContentsView dialogs - kiosk window with hidden menu, Ctrl+Shift+I devtools shortcut - Trusted Types disabled engine-wide so adblocker scriptlets work on YouTube Google OAuth handling (the hard-won part): Google's anti-abuse JS rejects WebContentsView + custom session settings as "embedded browser". So accounts.google.com opens in a top-level BrowserWindow popup in a dedicated persist:google-login partition that we never call setProxy/setUserAgent on — it inherits Windows system proxy and the default Electron-tagged UA, both of which Google accepts. After login, .google.com/.youtube.com cookies migrate into the parent view's session and the view reloads to pick up the logged-in state. Session restore: only the last-active tab attaches to the window; other tabs load silently in the background and become instantly visible when the user clicks them in the sidebar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
74 lines
3.0 KiB
TypeScript
74 lines
3.0 KiB
TypeScript
import React, { useState } from 'react'
|
||
import { Bookmark } from './Settings'
|
||
|
||
interface BookmarksBarProps {
|
||
bookmarks: Bookmark[]
|
||
onOpen: (b: Bookmark) => void
|
||
onRemove: (index: number) => void
|
||
}
|
||
|
||
const BookmarksBar: React.FC<BookmarksBarProps> = ({ bookmarks, onOpen, onRemove }) => {
|
||
const [expanded, setExpanded] = useState(false)
|
||
|
||
if (!bookmarks.length) return null
|
||
|
||
return (
|
||
<div className="bookmarks-bar">
|
||
<div className="bookmarks-bar-header" onClick={() => setExpanded(e => !e)}>
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" />
|
||
</svg>
|
||
<span>Закладки ({bookmarks.length})</span>
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
||
{expanded ? <polyline points="18 15 12 9 6 15" /> : <polyline points="6 9 12 15 18 9" />}
|
||
</svg>
|
||
</div>
|
||
|
||
<div className={`bookmarks-collapse${expanded ? ' open' : ''}`}>
|
||
<div className="bookmarks-collapse-inner">
|
||
<div className="bookmarks-list">
|
||
{bookmarks.map((b, i) => {
|
||
const hasMoviePoster = !!b.poster && b.poster !== b.siteIcon
|
||
return (
|
||
<div key={i} className="bookmark-card" onClick={() => onOpen(b)}>
|
||
<div className="bookmark-poster">
|
||
{b.poster
|
||
? <img src={b.poster} alt={b.title} onError={e => {
|
||
const t = e.currentTarget
|
||
t.style.display = 'none'
|
||
const ph = t.nextElementSibling as HTMLElement | null
|
||
if (ph) ph.style.display = 'flex'
|
||
}} />
|
||
: null}
|
||
<div className="bookmark-poster-placeholder" style={b.poster ? { display: 'none' } : undefined}>
|
||
{b.title.charAt(0).toUpperCase()}
|
||
</div>
|
||
</div>
|
||
<div className="bookmark-info">
|
||
<div className="bookmark-title" title={b.title}>{b.title}</div>
|
||
{b.source && (
|
||
<div className="bookmark-source-row">
|
||
{hasMoviePoster && b.siteIcon && (
|
||
<img className="bookmark-source-icon" src={b.siteIcon} alt="" onError={e => { e.currentTarget.style.display = 'none' }} />
|
||
)}
|
||
<span className="bookmark-source">{b.source}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
<button
|
||
className="bookmark-remove"
|
||
onClick={e => { e.stopPropagation(); onRemove(i) }}
|
||
title="Удалить закладку"
|
||
>✕</button>
|
||
</div>
|
||
)
|
||
})}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default BookmarksBar
|