feat: back-to-search button, retry site search, update checker, nsis installer
This commit is contained in:
25
main.js
25
main.js
@@ -157,6 +157,27 @@ async function loadExtensions() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Updates ---
|
||||
|
||||
async function checkForUpdates() {
|
||||
try {
|
||||
const res = await getDirectSession().fetch(
|
||||
'https://gitea.esh-service.ru/api/v1/repos/public/ESH-Media/releases/latest'
|
||||
);
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
const latest = (data.tag_name || '').replace(/^v/, '');
|
||||
const current = app.getVersion();
|
||||
if (latest && latest !== current) {
|
||||
mainWindow.webContents.send('update-available', {
|
||||
version: latest,
|
||||
url: data.html_url,
|
||||
assets: (data.assets || []).map(a => ({ name: a.name, url: a.browser_download_url })),
|
||||
});
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// --- Window ---
|
||||
|
||||
async function createWindow() {
|
||||
@@ -862,6 +883,10 @@ app.whenReady().then(async () => {
|
||||
getDirectSession();
|
||||
await loadExtensions();
|
||||
await createWindow();
|
||||
|
||||
mainWindow.webContents.once('did-finish-load', () => {
|
||||
setTimeout(checkForUpdates, 4000);
|
||||
});
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
|
||||
@@ -46,14 +46,12 @@
|
||||
"extensions/**/*"
|
||||
],
|
||||
"win": {
|
||||
"target": ["zip"],
|
||||
"target": ["nsis", "zip"],
|
||||
"icon": "public/favicon.ico"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"installerLanguages": ["Russian", "English"],
|
||||
"language": "1049"
|
||||
"allowToChangeInstallationDirectory": true
|
||||
},
|
||||
"linux": {
|
||||
"target": ["AppImage", "deb"],
|
||||
|
||||
@@ -10,9 +10,11 @@ interface HeaderProps {
|
||||
onBookmark: (title: string, url: string, poster: string, source: string) => void
|
||||
onBookmarkRemove: (index: number) => void
|
||||
bookmarks: import('./Settings').Bookmark[]
|
||||
openedFromSearch?: boolean
|
||||
onBackToSearch?: () => void
|
||||
}
|
||||
|
||||
const Header: React.FC<HeaderProps> = ({ activeApp, setActiveApp, onAppsChange, onMovieSearch, onBookmark, onBookmarkRemove, bookmarks }) => {
|
||||
const Header: React.FC<HeaderProps> = ({ activeApp, setActiveApp, onAppsChange, onMovieSearch, onBookmark, onBookmarkRemove, bookmarks, openedFromSearch, onBackToSearch }) => {
|
||||
const [isCollapsed, setIsCollapsed] = useState(false)
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
const [leftDisabled, setLeftDisabled] = useState(true)
|
||||
@@ -106,6 +108,15 @@ const Header: React.FC<HeaderProps> = ({ activeApp, setActiveApp, onAppsChange,
|
||||
const showSearchIcon = activeApp === 'home' || activeApp === 'movie-search'
|
||||
|
||||
const [isKiosk, setIsKiosk] = useState(true)
|
||||
const [updateInfo, setUpdateInfo] = useState<{ version: string; url: string } | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!window.electron) return
|
||||
const off = window.electron.on('update-available', (info: { version: string; url: string }) => {
|
||||
setUpdateInfo(info)
|
||||
})
|
||||
return off
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
window.electron?.isKiosk().then(k => setIsKiosk(k))
|
||||
@@ -185,6 +196,14 @@ const Header: React.FC<HeaderProps> = ({ activeApp, setActiveApp, onAppsChange,
|
||||
</div>
|
||||
|
||||
<div className="header-center">
|
||||
{appOpen && openedFromSearch && onBackToSearch && (
|
||||
<button className="header-btn nav-btn" onClick={onBackToSearch} title="Вернуться к результатам поиска">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#e53935" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="15 18 9 12 15 6" />
|
||||
</svg>
|
||||
<span style={{ fontSize: '12px', color: '#e53935', marginLeft: 2 }}>Поиск</span>
|
||||
</button>
|
||||
)}
|
||||
{appOpen && (
|
||||
<>
|
||||
<button
|
||||
@@ -262,6 +281,16 @@ const Header: React.FC<HeaderProps> = ({ activeApp, setActiveApp, onAppsChange,
|
||||
)}
|
||||
</div>
|
||||
|
||||
{updateInfo && (
|
||||
<div className="update-banner">
|
||||
<span>Доступна версия {updateInfo.version}</span>
|
||||
<a href={updateInfo.url} target="_blank" rel="noreferrer" className="update-banner-btn" onClick={() => window.electron?.createView('Обновление', updateInfo.url, '', 1.0, false)}>
|
||||
Скачать
|
||||
</a>
|
||||
<button className="update-banner-close" onClick={() => setUpdateInfo(null)}>✕</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showSettings && (
|
||||
<Settings onClose={closeSettings} onAppsChange={onAppsChange} />
|
||||
)}
|
||||
|
||||
@@ -468,7 +468,21 @@ const MovieSearch: React.FC<MovieSearchProps> = ({ onOpenUrl, onBookmark, initia
|
||||
<div className="ms-site-results">
|
||||
{sitesLoading && <p className="movie-search-message">Ищем на {sites.length} сайтах...</p>}
|
||||
{!sitesLoading && !siteResults.length && !message && <p className="movie-search-message">Поиск...</p>}
|
||||
{siteResults.length > 0 && <div className="ms-sites-label">Найдено на сайтах</div>}
|
||||
{!sitesLoading && siteResults.length > 0 && (
|
||||
<div className="ms-sites-label">
|
||||
Найдено на сайтах
|
||||
<button className="ms-retry-btn" onClick={() => selected && doSiteSearch(selected.title || selected.originalTitle, sites, selected.year, selected.mediaType)} title="Повторить поиск">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="23 4 23 10 17 10" /><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{!sitesLoading && !siteResults.length && (
|
||||
<button className="ms-retry-btn ms-retry-standalone" onClick={() => selected && doSiteSearch(selected.title || selected.originalTitle, sites, selected.year, selected.mediaType)}>
|
||||
Повторить поиск
|
||||
</button>
|
||||
)}
|
||||
{siteResults.map((r, i) => (
|
||||
<div key={i} className="ms-site-row" onClick={() => onOpenUrl(r.title, r.url)}>
|
||||
<span className="ms-site-source">{r.source}</span>
|
||||
|
||||
@@ -17,6 +17,8 @@ const HomePage: React.FC = () => {
|
||||
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>({})
|
||||
|
||||
@@ -46,14 +48,22 @@ const HomePage: React.FC = () => {
|
||||
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) => {
|
||||
const updated = [...bookmarks, { title, url, poster, source }]
|
||||
setBookmarks(updated)
|
||||
@@ -89,12 +99,14 @@ const HomePage: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header activeApp={activeApp} setActiveApp={setActiveApp} onAppsChange={setAppCardList} onMovieSearch={handleMovieSearch} onBookmark={handleBookmarkAdd} onBookmarkRemove={handleBookmarkRemove} bookmarks={bookmarks} />
|
||||
<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} />
|
||||
{activeApp === 'movie-search'
|
||||
? <MovieSearch initialQuery={movieQuery ?? ''} onOpenUrl={handleMovieSearchOpen} onBookmark={handleBookmarkAdd} />
|
||||
: <AppList apps={appCardList} bookmarks={bookmarks} onBookmarkOpen={handleBookmarkOpen} onBookmarkRemove={handleBookmarkRemove} />
|
||||
}
|
||||
<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} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1323,3 +1323,64 @@ body {
|
||||
.modal-btn-yes { background: #E50914; color: #fff; }
|
||||
.modal-btn-no { background: rgba(255,255,255,0.1); color: #ccc; }
|
||||
.modal-btn-ok { background: rgba(255,255,255,0.1); color: #ccc; }
|
||||
|
||||
/* ---- Update banner ---- */
|
||||
.update-banner {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 9999;
|
||||
background: #1a1a1a;
|
||||
border: 1px solid rgba(229,9,20,0.4);
|
||||
border-radius: 8px;
|
||||
padding: 10px 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
|
||||
}
|
||||
.update-banner-btn {
|
||||
background: #E50914;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
.update-banner-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #777;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
.update-banner-close:hover { color: #fff; }
|
||||
|
||||
/* ---- Retry btn ---- */
|
||||
.ms-retry-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #aaa;
|
||||
cursor: pointer;
|
||||
padding: 2px 6px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border-radius: 4px;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
.ms-retry-btn:hover { color: #fff; }
|
||||
.ms-retry-standalone {
|
||||
margin-top: 12px;
|
||||
font-size: 13px;
|
||||
color: #E50914;
|
||||
display: block;
|
||||
}
|
||||
.ms-retry-standalone:hover { color: #ff4444; }
|
||||
|
||||
Reference in New Issue
Block a user