mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-16 13:20:39 -07:00
Implement autoupdater functionality and enhance icon management
- Added a new UpdateNotification component to inform users about available updates and facilitate installation. - Integrated useAutoUpdater hook for managing update checks and installations. - Updated package.json with new build and generate scripts for release preparation and key generation. - Enhanced tauri configuration to support autoupdater with signing keys and endpoints. - Created scripts for preparing signed releases and updating icons, ensuring a streamlined workflow for asset management. - Added detailed documentation for autoupdater setup and icon update workflow.
This commit is contained in:
@@ -2,6 +2,7 @@ import { Download, Play, Trash2 } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { CircleButton } from '@/components/ui/circle-button';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -29,6 +30,8 @@ export function HistoryTable() {
|
||||
const setAudio = usePlayerStore((state) => state.setAudio);
|
||||
const currentAudioId = usePlayerStore((state) => state.audioId);
|
||||
const isPlaying = usePlayerStore((state) => state.isPlaying);
|
||||
const audioUrl = usePlayerStore((state) => state.audioUrl);
|
||||
const isPlayerVisible = !!audioUrl;
|
||||
|
||||
const handlePlay = (audioId: string, text: string) => {
|
||||
const audioUrl = apiClient.getAudioUrl(audioId);
|
||||
@@ -67,16 +70,19 @@ export function HistoryTable() {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex-1 min-h-0 overflow-y-auto border rounded-md max-h-[calc(100vh-280px)]">
|
||||
<Table>
|
||||
<div className={cn(
|
||||
"flex-1 min-h-0 overflow-y-auto border rounded-md overflow-x-hidden",
|
||||
isPlayerVisible && "max-h-[calc(100vh-280px)]"
|
||||
)}>
|
||||
<Table className="w-full table-fixed">
|
||||
<TableHeader className="sticky top-0 bg-background z-10">
|
||||
<TableRow>
|
||||
<TableHead>Text</TableHead>
|
||||
<TableHead>Profile</TableHead>
|
||||
<TableHead>Language</TableHead>
|
||||
<TableHead>Duration</TableHead>
|
||||
<TableHead>Created</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
<TableHead className="w-[40%]">Text</TableHead>
|
||||
<TableHead className="w-[15%]">Profile</TableHead>
|
||||
<TableHead className="w-[10%]">Language</TableHead>
|
||||
<TableHead className="w-[10%]">Duration</TableHead>
|
||||
<TableHead className="w-[15%]">Created</TableHead>
|
||||
<TableHead className="w-[10%] text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -85,45 +91,37 @@ export function HistoryTable() {
|
||||
return (
|
||||
<TableRow
|
||||
key={gen.id}
|
||||
className={cn(isCurrentlyPlaying && 'bg-muted/50')}
|
||||
className={cn(
|
||||
isCurrentlyPlaying && 'bg-muted/50',
|
||||
'cursor-pointer'
|
||||
)}
|
||||
onClick={() => handlePlay(gen.id, gen.text)}
|
||||
>
|
||||
<TableCell className="max-w-[200px] truncate">{gen.text}</TableCell>
|
||||
<TableCell>{gen.profile_name}</TableCell>
|
||||
<TableCell className="truncate">{gen.text}</TableCell>
|
||||
<TableCell className="truncate">{gen.profile_name}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">{gen.language}</Badge>
|
||||
<Badge variant="outline" className="text-xs">{gen.language}</Badge>
|
||||
</TableCell>
|
||||
<TableCell>{formatDuration(gen.duration)}</TableCell>
|
||||
<TableCell className="text-sm">{formatDate(gen.created_at)}</TableCell>
|
||||
<TableCell className="text-sm">{formatDuration(gen.duration)}</TableCell>
|
||||
<TableCell className="text-xs text-muted-foreground/60">{formatDate(gen.created_at)}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
<div className="flex justify-end gap-0.5" onClick={(e) => e.stopPropagation()}>
|
||||
<CircleButton
|
||||
icon={Play}
|
||||
onClick={() => handlePlay(gen.id, gen.text)}
|
||||
aria-label="Play audio"
|
||||
className={
|
||||
currentAudioId === gen.id && isPlaying ? 'text-primary' : ''
|
||||
}
|
||||
>
|
||||
<Play className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
/>
|
||||
<CircleButton
|
||||
icon={Download}
|
||||
onClick={() => handleDownload(gen.id, gen.text)}
|
||||
aria-label="Download audio"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
/>
|
||||
<CircleButton
|
||||
icon={Trash2}
|
||||
onClick={() => deleteGeneration.mutate(gen.id)}
|
||||
disabled={deleteGeneration.isPending}
|
||||
aria-label="Delete generation"
|
||||
>
|
||||
<Trash2 className="h-4 w-4 text-destructive" />
|
||||
</Button>
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useAutoUpdater } from '../hooks/useAutoUpdater';
|
||||
import { Button } from './ui/button';
|
||||
import { Card } from './ui/card';
|
||||
import { Progress } from './ui/progress';
|
||||
|
||||
export function UpdateNotification() {
|
||||
const { status, checkForUpdates, downloadAndInstall } = useAutoUpdater(true);
|
||||
|
||||
if (status.error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!status.available && !status.checking) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (status.checking) {
|
||||
return (
|
||||
<Card className="p-4 mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="animate-spin h-4 w-4 border-2 border-primary border-t-transparent rounded-full" />
|
||||
<span className="text-sm">Checking for updates...</span>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (status.available) {
|
||||
return (
|
||||
<Card className="p-4 mb-4 border-primary">
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<h3 className="font-semibold">Update Available</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Version {status.version} is ready to install
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{status.downloading && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm">Downloading update...</p>
|
||||
<Progress />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status.installing && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm">Installing update...</p>
|
||||
<p className="text-xs text-muted-foreground">App will restart automatically</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!status.downloading && !status.installing && (
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={downloadAndInstall} size="sm">
|
||||
Install Now
|
||||
</Button>
|
||||
<Button onClick={() => window.location.reload()} variant="outline" size="sm">
|
||||
Later
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user