feat: add auto-update functionality
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
9
frontend/wailsjs/go/app/App.d.ts
vendored
9
frontend/wailsjs/go/app/App.d.ts
vendored
@@ -1,15 +1,20 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
import {models} from '../models';
|
||||
import {updater} from '../models';
|
||||
|
||||
export function AddParticipant(arg1:string,arg2:string,arg3:number):Promise<models.Participant>;
|
||||
|
||||
export function CheckForUpdates():Promise<updater.UpdateInfo>;
|
||||
|
||||
export function DeleteAllSessions():Promise<void>;
|
||||
|
||||
export function DeleteParticipant(arg1:number):Promise<void>;
|
||||
|
||||
export function DeleteSession(arg1:number):Promise<void>;
|
||||
|
||||
export function DownloadAndInstallUpdate():Promise<void>;
|
||||
|
||||
export function ExportCSV(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function ExportData(arg1:string,arg2:string):Promise<string>;
|
||||
@@ -30,6 +35,8 @@ export function GetStatistics(arg1:string,arg2:string):Promise<models.Aggregated
|
||||
|
||||
export function GetTimerState():Promise<models.TimerState>;
|
||||
|
||||
export function GetVersion():Promise<string>;
|
||||
|
||||
export function NextSpeaker():Promise<void>;
|
||||
|
||||
export function PauseMeeting():Promise<void>;
|
||||
@@ -38,6 +45,8 @@ export function RemoveFromQueue(arg1:number):Promise<void>;
|
||||
|
||||
export function ReorderParticipants(arg1:Array<number>):Promise<void>;
|
||||
|
||||
export function RestartApp():Promise<void>;
|
||||
|
||||
export function ResumeMeeting():Promise<void>;
|
||||
|
||||
export function SkipSpeaker():Promise<void>;
|
||||
|
||||
@@ -6,6 +6,10 @@ export function AddParticipant(arg1, arg2, arg3) {
|
||||
return window['go']['app']['App']['AddParticipant'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function CheckForUpdates() {
|
||||
return window['go']['app']['App']['CheckForUpdates']();
|
||||
}
|
||||
|
||||
export function DeleteAllSessions() {
|
||||
return window['go']['app']['App']['DeleteAllSessions']();
|
||||
}
|
||||
@@ -18,6 +22,10 @@ export function DeleteSession(arg1) {
|
||||
return window['go']['app']['App']['DeleteSession'](arg1);
|
||||
}
|
||||
|
||||
export function DownloadAndInstallUpdate() {
|
||||
return window['go']['app']['App']['DownloadAndInstallUpdate']();
|
||||
}
|
||||
|
||||
export function ExportCSV(arg1, arg2) {
|
||||
return window['go']['app']['App']['ExportCSV'](arg1, arg2);
|
||||
}
|
||||
@@ -58,6 +66,10 @@ export function GetTimerState() {
|
||||
return window['go']['app']['App']['GetTimerState']();
|
||||
}
|
||||
|
||||
export function GetVersion() {
|
||||
return window['go']['app']['App']['GetVersion']();
|
||||
}
|
||||
|
||||
export function NextSpeaker() {
|
||||
return window['go']['app']['App']['NextSpeaker']();
|
||||
}
|
||||
@@ -74,6 +86,10 @@ export function ReorderParticipants(arg1) {
|
||||
return window['go']['app']['App']['ReorderParticipants'](arg1);
|
||||
}
|
||||
|
||||
export function RestartApp() {
|
||||
return window['go']['app']['App']['RestartApp']();
|
||||
}
|
||||
|
||||
export function ResumeMeeting() {
|
||||
return window['go']['app']['App']['ResumeMeeting']();
|
||||
}
|
||||
|
||||
@@ -432,3 +432,30 @@ export namespace models {
|
||||
|
||||
}
|
||||
|
||||
export namespace updater {
|
||||
|
||||
export class UpdateInfo {
|
||||
available: boolean;
|
||||
currentVersion: string;
|
||||
latestVersion: string;
|
||||
releaseNotes: string;
|
||||
downloadURL: string;
|
||||
downloadSize: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new UpdateInfo(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.available = source["available"];
|
||||
this.currentVersion = source["currentVersion"];
|
||||
this.latestVersion = source["latestVersion"];
|
||||
this.releaseNotes = source["releaseNotes"];
|
||||
this.downloadURL = source["downloadURL"];
|
||||
this.downloadSize = source["downloadSize"];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user