feat: add auto-update functionality

This commit is contained in:
Mikhail Kiselev
2026-02-10 15:39:17 +03:00
parent 87f424c26e
commit a81540646e
10 changed files with 727 additions and 13 deletions

View File

@@ -1,7 +1,7 @@
<script>
import { onMount, createEventDispatcher } from 'svelte'
import { GetSettings, UpdateSettings, GetMeeting, UpdateMeeting } from '../../wailsjs/go/app/App'
import { WindowSetSize, ScreenGetAll } from '../../wailsjs/runtime/runtime'
import { GetSettings, UpdateSettings, GetMeeting, UpdateMeeting, GetVersion, CheckForUpdates, DownloadAndInstallUpdate, RestartApp } from '../../wailsjs/go/app/App'
import { WindowSetSize, ScreenGetAll, EventsOn, EventsOff } from '../../wailsjs/runtime/runtime'
import { t, locale, setLocale } from '../lib/i18n'
const dispatch = createEventDispatcher()
@@ -16,6 +16,15 @@
let windowFullHeight = true
let audioContext = null
// Update state
let currentVersion = 'dev'
let updateInfo = null
let checkingUpdate = false
let downloadingUpdate = false
let downloadProgress = 0
let updateError = null
let updateComplete = false
function getAudioContext() {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)()
@@ -67,8 +76,63 @@
onMount(async () => {
await loadData()
// Load version and check for updates
try {
currentVersion = await GetVersion()
checkForUpdates()
} catch (e) {
console.error('Failed to get version:', e)
}
// Listen for update progress events
EventsOn('update:progress', (progress) => {
downloadProgress = progress
})
EventsOn('update:complete', () => {
downloadingUpdate = false
updateComplete = true
})
return () => {
EventsOff('update:progress')
EventsOff('update:complete')
}
})
async function checkForUpdates() {
checkingUpdate = true
updateError = null
updateInfo = null
try {
updateInfo = await CheckForUpdates()
} catch (e) {
console.error('Failed to check for updates:', e)
updateError = e.message || 'Unknown error'
} finally {
checkingUpdate = false
}
}
async function downloadAndInstall() {
downloadingUpdate = true
downloadProgress = 0
updateError = null
try {
await DownloadAndInstallUpdate()
} catch (e) {
console.error('Failed to download update:', e)
updateError = e.message || 'Download failed'
downloadingUpdate = false
}
}
async function restartApp() {
await RestartApp()
}
async function loadData() {
loading = true
try {
@@ -204,6 +268,66 @@
</div>
</section>
<section class="updates-section">
<h2>{$t('updates.title')}</h2>
<div class="version-info">
<span class="version-label">{$t('updates.currentVersion')}:</span>
<span class="version-value">{currentVersion}</span>
</div>
{#if checkingUpdate}
<div class="update-status checking">
<span class="spinner"></span>
{$t('updates.checkingForUpdates')}
</div>
{:else if updateError}
<div class="update-status error">
{$t('updates.error')}: {updateError}
</div>
<button class="update-btn" on:click={checkForUpdates}>
{$t('updates.checkNow')}
</button>
{:else if updateComplete}
<div class="update-status success">
{$t('updates.restartRequired')}
</div>
<div class="update-buttons">
<button class="update-btn primary" on:click={restartApp}>
{$t('updates.restart')}
</button>
<button class="update-btn" on:click={() => updateComplete = false}>
{$t('updates.later')}
</button>
</div>
{:else if downloadingUpdate}
<div class="update-status downloading">
{$t('updates.downloading')} {Math.round(downloadProgress * 100)}%
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: {downloadProgress * 100}%"></div>
</div>
{:else if updateInfo?.available}
<div class="update-status available">
{$t('updates.updateAvailable')}: <strong>{updateInfo.latestVersion}</strong>
</div>
<button class="update-btn primary" on:click={downloadAndInstall}>
{$t('updates.downloadAndInstall')}
</button>
{:else if updateInfo}
<div class="update-status uptodate">
{$t('updates.upToDate')}
</div>
<button class="update-btn" on:click={checkForUpdates}>
{$t('updates.checkNow')}
</button>
{:else}
<button class="update-btn" on:click={checkForUpdates}>
{$t('updates.checkNow')}
</button>
{/if}
</section>
<button class="save-btn" on:click={saveSettings} disabled={saving}>
{saving ? $t('common.loading') : $t('settings.save')}
</button>
@@ -369,4 +493,126 @@
.test-btn:active {
transform: scale(0.97);
}
/* Updates section */
.updates-section {
border: 1px solid #3d4f61;
}
.version-info {
display: flex;
gap: 8px;
margin-bottom: 12px;
font-size: 14px;
}
.version-label {
color: #9ca3af;
}
.version-value {
color: #4a90d9;
font-family: monospace;
}
.update-status {
padding: 10px;
border-radius: 8px;
margin-bottom: 12px;
font-size: 14px;
}
.update-status.checking {
background: #1b2636;
color: #9ca3af;
display: flex;
align-items: center;
gap: 8px;
}
.update-status.error {
background: #7f1d1d;
color: #fca5a5;
}
.update-status.available {
background: #164e63;
color: #67e8f9;
}
.update-status.uptodate {
background: #14532d;
color: #86efac;
}
.update-status.downloading {
background: #1e3a5f;
color: #93c5fd;
}
.update-status.success {
background: #14532d;
color: #86efac;
}
.spinner {
width: 16px;
height: 16px;
border: 2px solid #4a90d9;
border-top-color: transparent;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.progress-bar {
width: 100%;
height: 8px;
background: #1b2636;
border-radius: 4px;
overflow: hidden;
margin-bottom: 12px;
}
.progress-fill {
height: 100%;
background: #4a90d9;
transition: width 0.3s ease;
}
.update-buttons {
display: flex;
gap: 8px;
}
.update-btn {
padding: 10px 16px;
border: 2px solid #3d4f61;
border-radius: 8px;
background: #1b2636;
color: #9ca3af;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.update-btn:hover {
border-color: #4a90d9;
background: #2a3a4e;
color: #e0e0e0;
}
.update-btn.primary {
background: #4a90d9;
border-color: #4a90d9;
color: white;
}
.update-btn.primary:hover {
background: #3b7dc9;
border-color: #3b7dc9;
}
</style>

View File

@@ -108,6 +108,24 @@ export const translations = {
windowFullHeight: 'Окно на всю высоту экрана',
},
// Updates
updates: {
title: 'Обновления',
currentVersion: 'Текущая версия',
checkingForUpdates: 'Проверка обновлений...',
updateAvailable: 'Доступно обновление',
upToDate: 'У вас последняя версия',
downloadAndInstall: 'Скачать и установить',
downloading: 'Загрузка...',
installing: 'Установка...',
restartRequired: 'Для завершения обновления требуется перезапуск',
restart: 'Перезапустить',
later: 'Позже',
error: 'Ошибка проверки обновлений',
downloadError: 'Ошибка загрузки обновления',
checkNow: 'Проверить сейчас',
},
// Participant management
participants: {
title: 'Управление участниками',
@@ -257,6 +275,24 @@ export const translations = {
windowFullHeight: 'Full screen height window',
},
// Updates
updates: {
title: 'Updates',
currentVersion: 'Current version',
checkingForUpdates: 'Checking for updates...',
updateAvailable: 'Update available',
upToDate: 'You have the latest version',
downloadAndInstall: 'Download and install',
downloading: 'Downloading...',
installing: 'Installing...',
restartRequired: 'Restart required to complete the update',
restart: 'Restart',
later: 'Later',
error: 'Error checking for updates',
downloadError: 'Error downloading update',
checkNow: 'Check now',
},
// Participant management
participants: {
title: 'Manage Participants',