Files
ESH-Media/src/components/Header.tsx
2026-03-14 05:04:51 +03:00

244 lines
9.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect, useRef } from 'react'
import Settings from './Settings'
import { AppEntry } from './Settings'
interface HeaderProps {
activeApp: string
setActiveApp: (name: string) => void
onAppsChange: (apps: AppEntry[]) => void
onMovieSearch: (query: string) => void
onBookmark: (title: string, url: string, poster: string, source: string) => void
onBookmarkRemove: (index: number) => void
bookmarks: import('./Settings').Bookmark[]
}
const Header: React.FC<HeaderProps> = ({ activeApp, setActiveApp, onAppsChange, onMovieSearch, onBookmark, onBookmarkRemove, bookmarks }) => {
const [isCollapsed, setIsCollapsed] = useState(false)
const [isHovered, setIsHovered] = useState(false)
const [leftDisabled, setLeftDisabled] = useState(true)
const [rightDisabled, setRightDisabled] = useState(true)
const [refreshDisabled, setRefreshDisabled] = useState(true)
const [showSettings, setShowSettings] = useState(false)
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const activeAppRef = useRef(activeApp)
useEffect(() => { activeAppRef.current = activeApp }, [activeApp])
useEffect(() => {
if (!window.electron) return
const offCloseApp = window.electron.on('closeApp', () => {
window.electron!.removeView(activeAppRef.current)
setIsCollapsed(false)
setActiveApp('home')
})
const offWebButtons = window.electron.on('updateWebButtons', (app: { historyPosition: number; history: string[] }) => {
setLeftDisabled(app.historyPosition === 0)
setRightDisabled(app.historyPosition === app.history.length - 1)
setRefreshDisabled(false)
})
return () => { offCloseApp(); offWebButtons() }
}, [setActiveApp])
const closeCurrentApp = () => {
window.electron?.confirm('Закрыть приложение?', 'closeApp')
}
const openSettings = () => {
if (appOpen) window.electron?.hideView()
setShowSettings(true)
}
const closeSettings = () => {
setShowSettings(false)
if (appOpen) window.electron?.showView(activeApp)
}
const toggleCollapse = () => {
if (isCollapsed) {
setIsCollapsed(false)
setIsHovered(false)
window.electron?.expandWithHeader()
} else {
setIsCollapsed(true)
window.electron?.collapseWithHeader()
}
}
const handleMouseEnter = () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current)
timeoutRef.current = setTimeout(() => {
if (isCollapsed) {
setIsHovered(true)
window.electron?.expandWithHeader()
}
}, 150)
}
const handleMouseLeave = () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current)
timeoutRef.current = setTimeout(() => {
if (isCollapsed) {
setIsHovered(false)
window.electron?.collapseWithHeader()
}
}, 150)
}
const backwardPage = () => {
setLeftDisabled(true)
setRightDisabled(true)
setRefreshDisabled(true)
window.electron?.backwardPage()
}
const forwardPage = () => {
setLeftDisabled(true)
setRightDisabled(true)
setRefreshDisabled(true)
window.electron?.forwardPage()
}
const refreshPage = () => {
setRefreshDisabled(true)
window.electron?.refreshPage()
}
const appOpen = activeApp !== 'home' && activeApp !== 'movie-search'
const showSearchIcon = activeApp === 'home' || activeApp === 'movie-search'
const [isBookmarked, setIsBookmarked] = useState(false)
const handleBookmark = () => {
window.electron?.getCurrentPage().then((page: any) => {
if (!page) return
let pageHost = ''
try { pageHost = new URL(page.url).hostname } catch (_) {}
const existingIdx = bookmarks.findIndex(b => {
try { return new URL(b.url).hostname === pageHost } catch (_) { return false }
})
if (existingIdx !== -1) {
onBookmarkRemove(existingIdx)
setIsBookmarked(false)
} else {
onBookmark(page.name, page.url, page.imageUrl || '', '')
setIsBookmarked(true)
}
})
}
useEffect(() => {
if (!appOpen) { setIsBookmarked(false); return }
window.electron?.getCurrentPage().then((page: any) => {
if (!page) return
let pageHost = ''
try { pageHost = new URL(page.url).hostname } catch (_) {}
setIsBookmarked(bookmarks.some(b => {
try { return new URL(b.url).hostname === pageHost } catch (_) { return false }
}))
})
}, [activeApp, bookmarks, appOpen])
return (
<>
<div
className={`header ${isCollapsed && !isHovered ? 'collapsed' : 'expanded'}`}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{(!isCollapsed || isHovered) && (
<>
<div className="header-left">
<div className="header-btn" onClick={openSettings} title="Настройки">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#aaa" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="3" />
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
</svg>
</div>
</div>
<div className="header-center">
{appOpen && (
<>
<button
className={`header-btn nav-btn${leftDisabled ? ' disabled' : ''}`}
onClick={leftDisabled ? undefined : backwardPage}
title="Назад"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="15 18 9 12 15 6" />
</svg>
</button>
<button
className={`header-btn nav-btn${rightDisabled ? ' disabled' : ''}`}
onClick={rightDisabled ? undefined : forwardPage}
title="Вперёд"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="9 18 15 12 9 6" />
</svg>
</button>
<button
className={`header-btn nav-btn${refreshDisabled ? ' disabled' : ''}`}
onClick={refreshDisabled ? undefined : refreshPage}
title="Обновить"
>
<svg width="20" height="20" 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>
<button className="header-btn nav-btn" onClick={handleBookmark} title={isBookmarked ? 'Удалить закладку' : 'В закладки'}>
<svg width="18" height="18" viewBox="0 0 24 24" fill={isBookmarked ? '#f5c518' : 'none'} stroke={isBookmarked ? '#f5c518' : 'currentColor'} strokeWidth="2.5" 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>
</button>
<button
className="header-btn nav-btn"
onClick={toggleCollapse}
title={isCollapsed ? 'Развернуть шапку' : 'Свернуть шапку'}
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
{isCollapsed
? <polyline points="6 9 12 15 18 9" />
: <polyline points="18 15 12 9 6 15" />
}
</svg>
</button>
</>
)}
</div>
<div className="header-right">
{showSearchIcon && (
<button
className={`header-btn nav-btn${activeApp === 'movie-search' ? ' active' : ''}`}
onClick={() => onMovieSearch('')}
title="Поиск фильмов"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<circle cx="11" cy="11" r="7" />
<line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>
</button>
)}
{appOpen && (
<button className="header-btn header-close-btn" onClick={closeCurrentApp} title="Закрыть приложение">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
)}
</div>
</>
)}
</div>
{showSettings && (
<Settings onClose={closeSettings} onAppsChange={onAppsChange} />
)}
</>
)
}
export default Header