feat: Started on updates and workflows

This commit is contained in:
Tickbase
2025-05-18 21:05:32 +02:00
parent 2376690230
commit e633524465
18 changed files with 7471 additions and 454 deletions

View File

@@ -1,4 +1,5 @@
import { useAppContext } from '@/contexts/useAppContext'
import { UpdateChecker } from '@/components/updater'
import { useAppLogic } from '@/hooks'
import './styles/main.scss'
@@ -104,6 +105,7 @@ function App() {
onClose={handleDlcDialogClose}
onConfirm={handleDlcConfirm}
/>
<UpdateChecker />
</div>
</ErrorBoundary>
)

View File

@@ -0,0 +1,147 @@
import { useState, useEffect } from 'react'
import { check, type Update, type DownloadEvent } from '@tauri-apps/plugin-updater'
import { relaunch } from '@tauri-apps/plugin-process'
import { Button } from '@/components/buttons'
/**
* React component that checks for updates and provides
* UI for downloading and installing them
*/
const UpdateChecker = () => {
const [updateAvailable, setUpdateAvailable] = useState(false)
const [updateInfo, setUpdateInfo] = useState<Update | null>(null)
const [isChecking, setIsChecking] = useState(false)
const [isDownloading, setIsDownloading] = useState(false)
const [downloadProgress, setDownloadProgress] = useState(0)
const [error, setError] = useState<string | null>(null)
// Check for updates on component mount
useEffect(() => {
checkForUpdates()
}, [])
const checkForUpdates = async () => {
try {
setIsChecking(true)
setError(null)
// Check for updates
const update = await check()
if (update) {
console.log(`Update available: ${update.version}`)
setUpdateAvailable(true)
setUpdateInfo(update)
} else {
console.log('No updates available')
setUpdateAvailable(false)
}
} catch (err) {
console.error('Failed to check for updates:', err)
setError(`Failed to check for updates: ${err instanceof Error ? err.message : String(err)}`)
} finally {
setIsChecking(false)
}
}
const downloadAndInstallUpdate = async () => {
if (!updateInfo) return
try {
setIsDownloading(true)
setError(null)
let downloaded = 0
let contentLength = 0
// Download and install update
await updateInfo.downloadAndInstall((event: DownloadEvent) => {
switch (event.event) {
case 'Started':
// Started event includes contentLength
if ('contentLength' in event.data && typeof event.data.contentLength === 'number') {
contentLength = event.data.contentLength
console.log(`Started downloading ${contentLength} bytes`)
}
break
case 'Progress':
// Progress event includes chunkLength
if ('chunkLength' in event.data && typeof event.data.chunkLength === 'number' && contentLength > 0) {
downloaded += event.data.chunkLength
const progress = (downloaded / contentLength) * 100
setDownloadProgress(progress)
console.log(`Downloaded ${downloaded} from ${contentLength}`)
}
break
case 'Finished':
console.log('Download finished')
break
}
})
console.log('Update installed, relaunching application')
await relaunch()
} catch (err) {
console.error('Failed to download and install update:', err)
setError(`Failed to download and install update: ${err instanceof Error ? err.message : String(err)}`)
setIsDownloading(false)
}
}
if (isChecking) {
return <div className="update-checker">Checking for updates...</div>
}
if (error) {
return (
<div className="update-checker error">
<p>{error}</p>
<Button variant="primary" onClick={checkForUpdates}>Try Again</Button>
</div>
)
}
if (!updateAvailable || !updateInfo) {
return null // Don't show anything if there's no update
}
return (
<div className="update-checker">
<div className="update-info">
<h3>Update Available</h3>
<p>Version {updateInfo.version} is available to download.</p>
{updateInfo.body && <p className="update-notes">{updateInfo.body}</p>}
</div>
{isDownloading ? (
<div className="update-progress">
<div className="progress-bar-container">
<div
className="progress-bar"
style={{ width: `${downloadProgress}%` }}
/>
</div>
<p>Downloading: {Math.round(downloadProgress)}%</p>
</div>
) : (
<div className="update-actions">
<Button
variant="primary"
onClick={downloadAndInstallUpdate}
disabled={isDownloading}
>
Download & Install
</Button>
<Button
variant="secondary"
onClick={() => setUpdateAvailable(false)}
>
Later
</Button>
</div>
)}
</div>
)
}
export default UpdateChecker

View File

@@ -0,0 +1 @@
export { default as UpdateChecker } from './UpdateChecker'

View File

@@ -1 +1,2 @@
@forward './loading';
@forward './updater';

View File

@@ -0,0 +1,85 @@
@use '../../themes/index' as *;
@use '../../abstracts/index' as *;
/*
Update checker component styles
*/
.update-checker {
border-radius: var(--radius-md);
background-color: var(--elevated-bg);
padding: 1.25rem;
margin: 1rem 0;
border: 1px solid var(--border-soft);
box-shadow: var(--shadow-standard);
max-width: 500px;
position: fixed;
bottom: 20px;
right: 20px;
z-index: var(--z-modal) - 1;
&.error {
border-color: var(--danger);
background-color: var(--danger-soft);
}
.update-info {
margin-bottom: 1rem;
h3 {
font-size: 1.2rem;
color: var(--primary-color);
margin-bottom: 0.5rem;
font-weight: var(--bold);
}
p {
color: var(--text-secondary);
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.update-notes {
font-size: 0.85rem;
color: var(--text-soft);
max-height: 120px;
overflow-y: auto;
padding: 0.5rem;
background-color: rgba(0, 0, 0, 0.1);
border-radius: var(--radius-sm);
white-space: pre-line;
margin-top: 0.5rem;
@include custom-scrollbar;
}
}
.update-progress {
margin-top: 1rem;
.progress-bar-container {
height: 6px;
background-color: var(--border-soft);
border-radius: 3px;
margin-bottom: 0.5rem;
overflow: hidden;
}
.progress-bar {
height: 100%;
background-color: var(--primary-color);
border-radius: 3px;
transition: width 0.3s ease;
}
p {
font-size: 0.8rem;
color: var(--text-secondary);
text-align: right;
}
}
.update-actions {
display: flex;
gap: 0.75rem;
margin-top: 1rem;
}
}