Skip to content

Commit f34c98b

Browse files
committed
feat: updater logic fixed, ipc helpers options added, store refactor, throw error toast handler
1 parent 1c9cca5 commit f34c98b

File tree

11 files changed

+106
-53
lines changed

11 files changed

+106
-53
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ytdlp-gui",
3-
"version": "0.9.7",
3+
"version": "0.9.8",
44
"description": "venipa-electron-template",
55
"main": "./out/main/index.js",
66
"author": "Venipa <[email protected]>",

src/main/stores/AppStore.ts

+2
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,7 @@ export interface AppStore {
1515
clipboardMonitorAutoAdd: boolean
1616
concurrentDownloads: number
1717
}
18+
startMinimized: boolean
19+
startOnBoot: boolean
1820
beta: boolean
1921
}

src/main/stores/app.migrations.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { MAX_PARALLEL_DOWNLOADS } from '@main/trpc/ytdlp.core'
2+
import { Migration } from 'electron-conf'
3+
import { AppStore } from './AppStore'
4+
5+
const appStoreMigrations: Migration<AppStore>[] = [
6+
{
7+
version: 0,
8+
hook(instance, currentVersion) {
9+
instance.store.features.concurrentDownloads = MAX_PARALLEL_DOWNLOADS
10+
}
11+
},
12+
{
13+
version: 1,
14+
hook(instance, currentVersion) {
15+
instance.store.startMinimized = false
16+
instance.store.startOnBoot = true
17+
}
18+
}
19+
]
20+
21+
export default appStoreMigrations

src/main/stores/app.store.ts

+4-8
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,14 @@ import { createYmlStore } from '@shared/electron/store/createYmlStore'
33
import { PathsOf } from '@shared/electron/store/inferKey'
44
import { app } from 'electron'
55
import { AppStore } from './AppStore'
6+
import appStoreMigrations from './app.migrations'
67
export interface AppLicense {
78
code: string
89
expires: string
910
}
1011
const defaultDownloadsPath = app.getPath('downloads')
1112
const store = createYmlStore<AppStore>('app-settings', {
12-
migrations: [
13-
{
14-
version: 0,
15-
hook(instance, currentVersion) {
16-
instance.store.features.concurrentDownloads = MAX_PARALLEL_DOWNLOADS
17-
}
18-
}
19-
],
13+
migrations: appStoreMigrations,
2014
defaults: {
2115
ytdlp: {
2216
checkForUpdate: true,
@@ -32,6 +26,8 @@ const store = createYmlStore<AppStore>('app-settings', {
3226
clipboardMonitorAutoAdd: true,
3327
concurrentDownloads: MAX_PARALLEL_DOWNLOADS
3428
},
29+
startMinimized: false,
30+
startOnBoot: true,
3531
beta: false
3632
}
3733
})

src/main/trpc/internal.api.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,10 @@ export const internalRouter = router({
6262
}),
6363
downloadUpdate: publicProcedure.mutation(() => {
6464
try {
65-
return autoUpdater.downloadUpdate().then((s) => {
66-
setUpdateHandledByFrontend(true)
67-
return s
65+
setUpdateHandledByFrontend(true)
66+
return autoUpdater.downloadUpdate().catch((err) => {
67+
setUpdateHandledByFrontend(false)
68+
throw err
6869
})
6970
} catch (ex: any) {
7071
throw new TRPCError({ message: ex.message, code: 'INTERNAL_SERVER_ERROR' })

src/main/updater/index.ts

+29-26
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { appStore } from '@main/stores/app.store'
22
import { isProduction } from '@shared/config'
33
import { app, BrowserWindow, dialog } from 'electron'
4-
import { autoUpdater } from 'electron-updater'
4+
import { autoUpdater, UpdateInfo } from 'electron-updater'
55
import semver from 'semver'
66
const [GITHUB_AUTHOR, GITHUB_REPOSITORY] = import.meta.env.VITE_GITHUB_REPOSITORY?.split(
77
'/',
@@ -21,6 +21,29 @@ export function checkForUpdates() {
2121
.then((info) => (info && isUpdateInRange(info.updateInfo.version) && info) || null)
2222
}
2323
export function checkForUpdatesAndNotify() {}
24+
export async function proceedUpdateDialog(info: UpdateInfo) {
25+
const releaseNotes = (
26+
typeof info.releaseNotes === 'string'
27+
? info.releaseNotes
28+
: info.releaseNotes?.map((x) => x.note).join('\n')
29+
)
30+
?.replace(/<[^>]+>/g, '')
31+
.trimStart()
32+
return await dialog
33+
.showMessageBox({
34+
title: `Update available (${info.version})`,
35+
message: `Hey there is a new version which you can update to.\n\n${
36+
process.platform === 'win32' ? releaseNotes : info.releaseName
37+
}`,
38+
type: 'question',
39+
buttons: ['Update now', 'Update on quit', 'Cancel'],
40+
cancelId: -1
41+
})
42+
.then(({ response }) => {
43+
if (response === 0) autoUpdater.quitAndInstall()
44+
else if (response === 1) autoUpdater.autoInstallOnAppQuit = true
45+
})
46+
}
2447
export function attachAutoUpdaterIPC(win: BrowserWindow) {
2548
autoUpdater.on(
2649
'update-available',
@@ -31,36 +54,16 @@ export function attachAutoUpdaterIPC(win: BrowserWindow) {
3154
win.webContents.send('update-available', false)
3255
win.webContents.send('update-checking', false)
3356
})
34-
autoUpdater.on('download-progress', (info) =>
35-
win.webContents.send('update-download-progress', info)
36-
)
37-
autoUpdater.on('update-downloaded', (info) => win.webContents.send('update-download-done', info))
3857
autoUpdater.on('checking-for-update', () =>
3958
win.webContents.send('update-checking', new Date().toISOString())
4059
)
60+
autoUpdater.signals.progress((info) => {
61+
win.webContents.send('update-download-progress', info)
62+
})
4163
autoUpdater.signals.updateDownloaded(async (x) => {
64+
win.webContents.send('update-download-done', x)
4265
if (updateQueuedInFrontend) return
43-
const releaseNotes = (
44-
typeof x.releaseNotes === 'string'
45-
? x.releaseNotes
46-
: x.releaseNotes?.map((x) => x.note).join('\n')
47-
)
48-
?.replace(/<[^>]+>/g, '')
49-
.trimStart()
50-
return await dialog
51-
.showMessageBox({
52-
title: `Update available (${x.version})`,
53-
message: `Hey there is a new version which you can update to.\n\n${
54-
process.platform === 'win32' ? releaseNotes : x.releaseName
55-
}`,
56-
type: 'question',
57-
buttons: ['Update now', 'Update on quit', 'Cancel'],
58-
cancelId: -1
59-
})
60-
.then(({ response }) => {
61-
if (response === 0) autoUpdater.quitAndInstall()
62-
else if (response === 1) autoUpdater.autoInstallOnAppQuit = true
63-
})
66+
return await proceedUpdateDialog(x)
6467
})
6568
if (!import.meta.env.VITE_GITHUB_REPOSITORY)
6669
autoUpdater.setFeedURL({

src/renderer/src/components/ui/app-navbar.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default function AppNavBar({
2121
const { pathname } = useLocation()
2222
const isQuitRoute = useMemo(() => quitPaths.includes(pathname), [pathname])
2323
const { windowState } = useWindowState()
24-
const { hide: onClose, close, maximize: onMaximize, minimize: onMinimize } = useWindowControls()
24+
const { hide, close, maximize: onMaximize, minimize: onMinimize } = useWindowControls()
2525
useEventListener(
2626
'keydown',
2727
(ev) => {
@@ -36,7 +36,7 @@ export default function AppNavBar({
3636
title={configState?.title || <Spinner />}
3737
state={{ ...windowState, title: '' } as any}
3838
className={cn('h-10 px-1.5', className)}
39-
{...{ onMinimize, onMaximize, onClose: isQuitRoute ? close : onClose }}
39+
{...{ onMinimize, onMaximize, onClose: isQuitRoute ? close : hide }}
4040
{...props}
4141
>
4242
{windowState && (

src/renderer/src/hooks/use-ipc.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@ import usePromise from './use-promise'
66

77
interface IpcOptions<T = any> {
88
defaultValue: T
9+
getOnInit?: boolean
910
}
10-
export function useIPC<T = any>(eventName: string, options: IpcOptions<T> = {} as any) {
11+
export function useIPC<T = any>(
12+
eventName: string,
13+
options: IpcOptions<T> = { getOnInit: false } as IpcOptions<T>
14+
) {
1115
const [state, setState] = useState(options.defaultValue)
1216
const handle = useCallback(
1317
(ev: IpcRendererEvent, data: any) => {
@@ -16,12 +20,13 @@ export function useIPC<T = any>(eventName: string, options: IpcOptions<T> = {} a
1620
[eventName]
1721
)
1822
useIsomorphicLayoutEffect(() => {
19-
window.api
20-
.invoke(`action:${eventName}`)
21-
.then((initialData) => setState(initialData))
22-
.catch((err) => {
23-
logger.error(err)
24-
})
23+
if (options.getOnInit)
24+
window.api
25+
.invoke(`action:${eventName}`)
26+
.then((initialData) => setState(initialData))
27+
.catch((err) => {
28+
logger.error(err)
29+
})
2530
}, [eventName])
2631
useEffect(() => {
2732
window.api.on(eventName, handle)

src/renderer/src/pages/components/status-bar.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ export default function StatusBar() {
6666
</>
6767
) : updateProgress && !updateDone ? (
6868
<>
69-
<Appear>Downloading... {String(updateProgress.percent).padStart(3, ' ')}%</Appear>
69+
<Appear>Downloading... {(updateProgress.percent as number).toFixed(2).padStart(5, ' ')}%</Appear>
7070
<div className="h-10 w-px bg-muted"></div>
7171
</>
72-
) : updateAvailable ? (
72+
) : updateAvailable && !updateDone ? (
7373
<>
7474
<Appear>Update Available, preparing...</Appear>
7575
<div className="h-10 w-px bg-muted"></div>

src/renderer/src/pages/sections/about.tsx

+20-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ClickableText from '@renderer/components/ui/clickable-text'
33
import { QTooltip } from '@renderer/components/ui/tooltip'
44
import { trpc } from '@renderer/lib/trpc-link'
55
import config, { NodeEnv } from '@shared/config'
6+
import { throwErrorToast } from '@shared/trpc/error'
67
import { formatDistanceToNow, isValid } from 'date-fns'
78
import { DotIcon } from 'lucide-react'
89
import { useMemo, useState } from 'react'
@@ -41,10 +42,18 @@ export default function AboutTab() {
4142
toast.info('A new version has been found', {
4243
action: (
4344
<div className="flex gap-1 ml-auto">
44-
<Button className="h-6 px-2 text-xs" variant={"ghost"} onClick={() => resolve(true)}>
45+
<Button
46+
className="h-6 px-2 text-xs"
47+
variant={'ghost'}
48+
onClick={() => resolve(true)}
49+
>
4550
Update Now
4651
</Button>
47-
<Button className="h-6 px-2 text-xs" variant={"ghost"} onClick={() => resolve(false)}>
52+
<Button
53+
className="h-6 px-2 text-xs"
54+
variant={'ghost'}
55+
onClick={() => resolve(false)}
56+
>
4857
Later
4958
</Button>
5059
</div>
@@ -62,9 +71,16 @@ export default function AboutTab() {
6271
duration: 0
6372
})
6473
await downloadUpdate().then(() => {
65-
toast.success('Update downloaded...', { description: 'Awaiting installation.', id })
74+
toast.success('Update downloaded...', {
75+
description: 'Awaiting installation.',
76+
dismissible: true,
77+
duration: 3000,
78+
id
79+
})
80+
})
81+
await quitAndInstallUpdate().catch((err) => {
82+
throwErrorToast(err)
6683
})
67-
await quitAndInstallUpdate()
6884
} else {
6985
toast.info('Update postponed.', { id, duration: 5000, description: null })
7086
}

src/shared/trpc/error.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { logger } from '@shared/logger'
2+
import { toast } from 'sonner'
3+
import { isTRPCErrorResponse } from './utils'
4+
5+
export function throwErrorToast(err: any) {
6+
if (!isTRPCErrorResponse(err)) throw err
7+
logger.error(err)
8+
return toast.error(err.message)
9+
}

0 commit comments

Comments
 (0)