init
This commit is contained in:
243
src/components/Header.tsx
Normal file
243
src/components/Header.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
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
|
||||
Reference in New Issue
Block a user