Skip to content

Commit 50e4b7c

Browse files
authored
feat: improve auto update (#33)
* feat: improve auto update * fix: fixing the code smell
1 parent bbc0eea commit 50e4b7c

File tree

7 files changed

+231
-15
lines changed

7 files changed

+231
-15
lines changed

src/main/autoupdate.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { autoUpdater } from 'electron-updater';
2+
import log from 'electron-log';
3+
import { BrowserWindow, ipcMain } from 'electron';
4+
5+
let globalCheckForUpdate = false;
6+
7+
export default function handleAutoUpdate(window: BrowserWindow) {
8+
log.transports.file.level = 'info';
9+
autoUpdater.logger = log;
10+
11+
autoUpdater.addListener('checking-for-update', () => {
12+
window.webContents.send('checking-for-update');
13+
});
14+
15+
autoUpdater.addListener('update-available', (e) => {
16+
window.webContents.send('update-available', e);
17+
});
18+
19+
autoUpdater.addListener('update-not-available', (e) => {
20+
window.webContents.send('update-not-available', e);
21+
});
22+
23+
autoUpdater.addListener('download-progress', (e) => {
24+
window.webContents.send('update-download-progress', e);
25+
});
26+
27+
autoUpdater.addListener('update-downloaded', (e) => {
28+
window.webContents.send('update-downloaded', e);
29+
});
30+
31+
ipcMain.handle('check-for-updates', () => {
32+
if (globalCheckForUpdate) return;
33+
globalCheckForUpdate = true;
34+
autoUpdater.checkForUpdates();
35+
});
36+
37+
ipcMain.handle('quit-and-install', () => {
38+
autoUpdater.quitAndInstall();
39+
});
40+
}

src/main/main.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,20 @@
88
*/
99
import path from 'path';
1010
import { app, BrowserWindow, shell, ipcMain } from 'electron';
11-
import { autoUpdater } from 'electron-updater';
12-
import log from 'electron-log';
1311
import { resolveHtmlPath } from './util';
1412

1513
import electronDebug from 'electron-debug';
1614
import sourceMapSupport from 'source-map-support';
1715
import OtherIpcHandler from './ipc/other';
1816
import ConnectionIpcHandler from './ipc/handleConnection';
17+
import handleAutoUpdate from './autoupdate';
1918

2019
const otherIpcHandler = new OtherIpcHandler();
2120
const connectionIpcHandler = new ConnectionIpcHandler();
2221

2322
otherIpcHandler.register();
2423
connectionIpcHandler.register();
2524

26-
class AppUpdater {
27-
constructor() {
28-
log.transports.file.level = 'info';
29-
autoUpdater.logger = log;
30-
autoUpdater.checkForUpdatesAndNotify();
31-
}
32-
}
33-
3425
let mainWindow: BrowserWindow | null = null;
3526

3627
ipcMain.on('ipc-example', async (event, arg) => {
@@ -99,9 +90,7 @@ const createWindow = async () => {
9990
return { action: 'deny' };
10091
});
10192

102-
// Remove this if your app does not use auto updates
103-
// eslint-disable-next-line
104-
new AppUpdater();
93+
handleAutoUpdate(mainWindow);
10594
};
10695

10796
/**

src/main/preload.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import {
88
MessageBoxSyncOptions,
99
MenuItemConstructorOptions,
1010
} from 'electron';
11+
import {
12+
ProgressInfo,
13+
UpdateDownloadedEvent,
14+
UpdateInfo,
15+
} from 'electron-updater';
1116

1217
export type Channels = 'ipc-example' | 'create-connection';
1318

@@ -50,6 +55,9 @@ const electronHandler = {
5055
setNativeMenu: (options: MenuItemConstructorOptions[]) =>
5156
ipcRenderer.invoke('set-menu', [options]),
5257

58+
quitAndInstall: () => ipcRenderer.invoke('quit-and-install'),
59+
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
60+
5361
handleMenuClick: (
5462
callback: (event: IpcRendererEvent, id: string) => void
5563
) => {
@@ -59,6 +67,39 @@ const electronHandler = {
5967
cacheHandleMenuClickCb = callback;
6068
return ipcRenderer.on('native-menu-click', callback);
6169
},
70+
71+
listenCheckingForUpdate: (callback: () => void) => {
72+
ipcRenderer.removeAllListeners('checking-for-update');
73+
return ipcRenderer.on('checking-for-update', callback);
74+
},
75+
76+
listenUpdateAvailable: (
77+
callback: (event: IpcRendererEvent, e: UpdateInfo) => void
78+
) => {
79+
ipcRenderer.removeAllListeners('update-available');
80+
return ipcRenderer.on('update-available', callback);
81+
},
82+
83+
listenUpdateNotAvailable: (
84+
callback: (event: IpcRendererEvent, e: UpdateInfo) => void
85+
) => {
86+
ipcRenderer.removeAllListeners('update-not-available');
87+
return ipcRenderer.on('update-not-available', callback);
88+
},
89+
90+
listenUpdateDownloadProgress: (
91+
callback: (event: IpcRendererEvent, e: ProgressInfo) => void
92+
) => {
93+
ipcRenderer.removeAllListeners('update-download-progress');
94+
return ipcRenderer.on('update-download-progress', callback);
95+
},
96+
97+
listenUpdateDownloaded: (
98+
callback: (event: IpcRendererEvent, e: UpdateDownloadedEvent) => void
99+
) => {
100+
ipcRenderer.removeAllListeners('update-downloaded');
101+
return ipcRenderer.on('update-downloaded', callback);
102+
},
62103
};
63104

64105
contextBridge.exposeInMainWorld('electron', electronHandler);

src/renderer/App.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { DialogProvider } from './contexts/DialogProvider';
99
import AppFeatureContext from './contexts/AppFeatureProvider';
1010
import NativeMenuProvider from './contexts/NativeMenuProvider';
1111
import NotImplementCallback from 'libs/NotImplementCallback';
12+
import Layout from './components/Layout';
13+
import StatusBar from './components/StatusBar';
1214

1315
const ConnectionContext = createContext<{
1416
connect: (connectionConfig: ConnectionStoreItem) => void;
@@ -47,7 +49,18 @@ export default function App() {
4749
<NativeMenuProvider>
4850
<SqlExecuteProvider>
4951
<div style={{ width: '100vw', height: '100vh' }}>
50-
{config ? <DatabaseScreen config={config} /> : <HomeScreen />}
52+
<Layout>
53+
<Layout.Grow>
54+
{config ? (
55+
<DatabaseScreen config={config} />
56+
) : (
57+
<HomeScreen />
58+
)}
59+
</Layout.Grow>
60+
<Layout.Fixed>
61+
<StatusBar />
62+
</Layout.Fixed>
63+
</Layout>
5164
</div>
5265
</SqlExecuteProvider>
5366
</NativeMenuProvider>

src/renderer/components/Splitter/styles.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
.splitter-layout .layout-pane {
1111
position: relative;
1212
flex: 0 0 auto;
13-
overflow: auto;
13+
overflow: hidden;
1414
}
1515

1616
.splitter-layout .layout-pane.layout-pane-primary {
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { useEffect, useState } from 'react';
2+
import styles from './styles.module.css';
3+
import pkg from './../../../../package.json';
4+
import Button from '../Button';
5+
import Stack from '../Stack';
6+
import ButtonGroup from '../ButtonGroup';
7+
8+
function StatusBarAutoUpdate() {
9+
const [autoUpdateMessage, setAutoUpdateMessage] = useState('');
10+
const [autoUpdateProgress, setAutoUpdateProgress] = useState(0);
11+
const [downloadCompleted, setDownloadCompleted] = useState(false);
12+
const [showUpdateModal, setShowUpdateModal] = useState(false);
13+
14+
useEffect(() => {
15+
window.electron.listenCheckingForUpdate(() =>
16+
setAutoUpdateMessage('Checking for update...')
17+
);
18+
19+
window.electron.listenUpdateNotAvailable(() => {
20+
setAutoUpdateMessage('Up to date');
21+
});
22+
23+
window.electron.listenUpdateAvailable(console.log);
24+
25+
window.electron.listenUpdateDownloadProgress((_, e) => {
26+
setAutoUpdateProgress(e.percent);
27+
});
28+
29+
window.electron.listenUpdateDownloaded(() => {
30+
setDownloadCompleted(true);
31+
setShowUpdateModal(true);
32+
});
33+
34+
window.electron.checkForUpdates();
35+
console.log('Check for update');
36+
}, [setAutoUpdateMessage, setDownloadCompleted, setShowUpdateModal]);
37+
38+
if (downloadCompleted) {
39+
return (
40+
<>
41+
{showUpdateModal && (
42+
<div className={styles.popup}>
43+
<Stack vertical spacing="md">
44+
<h1>New Update!</h1>
45+
<p>
46+
There is new update. Restart QueryMaster to use the latest
47+
version.
48+
</p>
49+
50+
<ButtonGroup>
51+
<Button
52+
primary
53+
onClick={() => window.electron.quitAndInstall()}
54+
>
55+
Restart
56+
</Button>
57+
<Button primary onClick={() => setShowUpdateModal(false)}>
58+
Later
59+
</Button>
60+
</ButtonGroup>
61+
</Stack>
62+
</div>
63+
)}
64+
<li>New update is available. Restart for update</li>
65+
</>
66+
);
67+
}
68+
69+
if (autoUpdateProgress > 0) {
70+
return (
71+
<li style={{ display: 'flex', gap: 10 }}>
72+
Downloading Update ({autoUpdateProgress.toFixed(0)}%)
73+
<div
74+
style={{
75+
width: 150,
76+
background: '#fffa',
77+
height: '100%',
78+
}}
79+
>
80+
<div
81+
style={{
82+
width: `${autoUpdateProgress}%`,
83+
background: '#16a085',
84+
height: '100%',
85+
}}
86+
></div>
87+
</div>
88+
</li>
89+
);
90+
}
91+
92+
return <li>{autoUpdateMessage}</li>;
93+
}
94+
95+
export default function StatusBar() {
96+
return (
97+
<div className={styles.statusBarContainer}>
98+
<ul>
99+
<li>QueryMaster v{pkg.version}</li>
100+
<li style={{ flexGrow: 1 }}></li>
101+
<StatusBarAutoUpdate />
102+
</ul>
103+
</div>
104+
);
105+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
.statusBarContainer {
2+
padding: 5px 10px;
3+
background: #8e44ad;
4+
color: #fff;
5+
font-size: 0.9rem;
6+
}
7+
8+
.statusBarContainer ul {
9+
list-style: none;
10+
display: flex;
11+
}
12+
13+
.popup {
14+
font-size: 1rem;
15+
width: 300px;
16+
background: var(--color-surface);
17+
position: fixed;
18+
z-index: 100;
19+
padding: 20px;
20+
right: 10px;
21+
bottom: 40px;
22+
box-shadow: var(--color-shadow) 0px 3px 8px;
23+
border: 1px solid var(--color-surface-hover);
24+
}
25+
26+
.popup h1 {
27+
font-size: 1.5rem;
28+
}

0 commit comments

Comments
 (0)