feat: seamless auto-update via electron-updater, multi-select filters, session restore
- electron-updater wired with Gitea API discovery: setFeedURL dynamically
per release (Gitea 1.24.7 lacks /releases/latest/download/ shortcut).
Differential download via .blockmap saves ~70 MB per patch. Renderer
banner shows states: available → downloading X% → ready. User clicks
"Установить и перезапустить" → quitAndInstall replaces files + relaunches.
- Multi-select filters per user spec: genres AND (TMDB with_genres comma-
joined), countries OR (pipe-joined into with_origin_country /
with_original_language), years OR (fan-out one request per year, merge
by id since TMDB has no discrete-year OR). Rating stays single threshold.
- Session persistence: openedSession {tabs, activeName} saved to config
on tab create/show/hide/remove/in-app navigation, plus before-quit.
Restored after did-finish-load via ipcMain.emit('create-view',...) per
tab. Survives auto-update relaunch — bring user back to the same page.
- electron-builder publish config (generic provider) so latest.yml is
generated during build.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -110,12 +110,24 @@ 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; currentVersion?: string; releaseUrl: string; installerUrl: string; installerName?: string } | null>(null)
|
||||
type UpdateStatus =
|
||||
| { state: 'available'; version: string; currentVersion?: string }
|
||||
| { state: 'downloading'; percent: number; bytesPerSecond?: number; transferred?: number; total?: number; version?: string; currentVersion?: string }
|
||||
| { state: 'ready'; version: string; currentVersion?: string }
|
||||
| { state: 'manual'; version: string; currentVersion?: string; installerUrl: string; installerName?: string }
|
||||
| { state: 'error'; message: string }
|
||||
const [updateStatus, setUpdateStatus] = useState<UpdateStatus | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!window.electron) return
|
||||
const off = window.electron.on('update-available', (info: { version: string; currentVersion?: string; releaseUrl: string; installerUrl: string; installerName?: string }) => {
|
||||
setUpdateInfo(info)
|
||||
const off = window.electron.on('update-status', (info: UpdateStatus) => {
|
||||
setUpdateStatus(prev => {
|
||||
// Preserve version across state transitions when payload omits it (download-progress)
|
||||
if (info.state === 'downloading' && prev && 'version' in prev && prev.version) {
|
||||
return { ...info, version: info.version || prev.version, currentVersion: info.currentVersion || ('currentVersion' in prev ? prev.currentVersion : undefined) }
|
||||
}
|
||||
return info
|
||||
})
|
||||
})
|
||||
return off
|
||||
}, [])
|
||||
@@ -278,13 +290,39 @@ const Header: React.FC<HeaderProps> = ({ activeApp, setActiveApp, onAppsChange,
|
||||
)}
|
||||
</div>
|
||||
|
||||
{updateInfo && (
|
||||
{updateStatus && updateStatus.state !== 'error' && (
|
||||
<div className="update-banner">
|
||||
<span>Доступна версия {updateInfo.version}{updateInfo.currentVersion ? ` (текущая ${updateInfo.currentVersion})` : ''}</span>
|
||||
<button className="update-banner-btn" onClick={() => window.electron?.createView('Обновление', updateInfo.installerUrl, '', 1.0, false)}>
|
||||
{updateInfo.installerName ? 'Скачать установщик' : 'Открыть релиз'}
|
||||
</button>
|
||||
<button className="update-banner-close" onClick={() => setUpdateInfo(null)}>✕</button>
|
||||
{updateStatus.state === 'available' && (
|
||||
<>
|
||||
<span className="update-banner-spinner" />
|
||||
<span>Загружается обновление {updateStatus.version}{updateStatus.currentVersion ? ` (текущая ${updateStatus.currentVersion})` : ''}…</span>
|
||||
</>
|
||||
)}
|
||||
{updateStatus.state === 'downloading' && (
|
||||
<>
|
||||
<span>Скачивается {updateStatus.version || 'обновление'}: {updateStatus.percent}%</span>
|
||||
<div className="update-banner-progress">
|
||||
<div className="update-banner-progress-bar" style={{ width: `${updateStatus.percent}%` }} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{updateStatus.state === 'ready' && (
|
||||
<>
|
||||
<span>Версия {updateStatus.version} готова к установке</span>
|
||||
<button className="update-banner-btn" onClick={() => window.electron?.installUpdate?.()}>
|
||||
Установить и перезапустить
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{updateStatus.state === 'manual' && (
|
||||
<>
|
||||
<span>Доступна {updateStatus.version}{updateStatus.currentVersion ? ` (текущая ${updateStatus.currentVersion})` : ''}</span>
|
||||
<button className="update-banner-btn" onClick={() => window.electron?.createView('Обновление', updateStatus.installerUrl, '', 1.0, false)}>
|
||||
Скачать установщик
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button className="update-banner-close" onClick={() => setUpdateStatus(null)}>✕</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user