From 07f3854019afcb82ea0722f1b99161d71001f108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=89=E9=9E=8B=E6=B2=A1=E5=8F=B7?= <308487730@qq.com> Date: Sun, 5 Mar 2023 10:39:45 +0800 Subject: [PATCH] refactor: cleanup --- electron/main/index.ts | 4 +- electron/main/update.ts | 70 ++++++ electron/preload/update.ts | 95 -------- package.json | 6 +- src/App.tsx | 4 +- src/components/update/Modal/index.tsx | 117 +++++----- src/components/update/Modal/modal.module.scss | 138 +++++++----- src/components/update/Modal/type.d.ts | 20 -- src/components/update/Progress/index.tsx | 34 +-- .../update/Progress/progress.module.scss | 5 +- src/components/update/Progress/type.d.ts | 5 - src/components/update/electron-updater.d.ts | 10 + src/components/update/index.tsx | 208 +++++++++--------- src/components/update/type.d.ts | 18 -- src/components/update/update.module.scss | 25 ++- 15 files changed, 374 insertions(+), 385 deletions(-) create mode 100644 electron/main/update.ts delete mode 100644 electron/preload/update.ts delete mode 100644 src/components/update/Modal/type.d.ts delete mode 100644 src/components/update/Progress/type.d.ts create mode 100644 src/components/update/electron-updater.d.ts delete mode 100644 src/components/update/type.d.ts diff --git a/electron/main/index.ts b/electron/main/index.ts index a4540b7..0deb058 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -1,7 +1,7 @@ import { app, BrowserWindow, shell, ipcMain } from 'electron' import { release } from 'node:os' import { join } from 'node:path' -import { update } from '../preload/update' +import { update } from './update' // The built directory structure // @@ -73,6 +73,8 @@ async function createWindow() { if (url.startsWith('https:')) shell.openExternal(url) return { action: 'deny' } }) + + // Apply electron-updater update(win) } diff --git a/electron/main/update.ts b/electron/main/update.ts new file mode 100644 index 0000000..d080ff1 --- /dev/null +++ b/electron/main/update.ts @@ -0,0 +1,70 @@ +import { app, ipcMain } from 'electron' +import { + type ProgressInfo, + type UpdateDownloadedEvent, + autoUpdater +} from 'electron-updater' + +export function update(win: Electron.BrowserWindow) { + + // When set to false, the update download will be triggered through the API + autoUpdater.autoDownload = false + + autoUpdater.disableWebInstaller = false + + autoUpdater.allowDowngrade = false + + // start check + autoUpdater.on('checking-for-update', function () { }) + // update available + autoUpdater.on('update-available', (arg) => { + win.webContents.send('update-can-available', { update: true, version: app.getVersion(), newVersion: arg?.version }) + }) + // update not available + autoUpdater.on('update-not-available', (arg) => { + win.webContents.send('update-can-available', { update: false, version: app.getVersion(), newVersion: arg?.version }) + }) + + // Checking for updates + ipcMain.handle('check-update', async () => { + try { + return await autoUpdater.checkForUpdatesAndNotify() + } catch (error) { + return { message: 'Network error', error } + } + }) + + // Start downloading and feedback on progress + ipcMain.handle('start-download', (event) => { + startDownload( + (error, progressInfo) => { + if (error) { + // feedback download error message + event.sender.send('update-error', { message: error.message, error }) + } else { + // feedback update progress message + event.sender.send('download-progress', progressInfo) + } + }, + () => { + // feedback update downloaded message + event.sender.send('update-downloaded') + } + ) + }) + + // Install now + ipcMain.handle('quit-and-install', () => { + autoUpdater.quitAndInstall(false, true) + }) +} + +function startDownload( + callback: (error: Error | null, info: ProgressInfo) => void, + complete: (evnet: UpdateDownloadedEvent) => void, +) { + autoUpdater.on('download-progress', info => callback(null, info)) + autoUpdater.on('error', error => callback(error, null)) + autoUpdater.on('update-downloaded', complete) + autoUpdater.downloadUpdate() +} diff --git a/electron/preload/update.ts b/electron/preload/update.ts deleted file mode 100644 index 76b0a05..0000000 --- a/electron/preload/update.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { autoUpdater } from "electron-updater" -import { app, ipcMain } from "electron"; -export const update = (win: Electron.CrossProcessExports.BrowserWindow) => { - - // When set to false, the update download will be triggered through the API - autoUpdater.autoDownload = false; - - autoUpdater.disableWebInstaller = false - - autoUpdater.allowDowngrade = false; - - // Save the version status of whether the update needs to be installed, - // Because the user needs to update immediately and later after the update is downloaded - let NEED_INSTALL = false; - - // Check whether update is used - ipcMain.on('check-update',()=>{ - autoUpdater.checkForUpdatesAndNotify() - .then((res) => { - win.webContents.send('check-update-type',{ checkUpdate: true}) - }).catch(err => { - // network error - win.webContents.send('check-update-type', { checkUpdate: false}) - }); - }) - - // start check - autoUpdater.on('checking-for-update', function () { - console.log('checking-for-update') - }) - // update available - autoUpdater.on('update-available', (arg) => { - console.log('update-available') - win.webContents.send('is-update-available', { isUpdate: true, oldVersion: app.getVersion(), newVersion: arg?.version }) - }) - // update not available - autoUpdater.on('update-not-available', (arg) => { - console.log('update-not-available') - win.webContents.send('is-update-available', { isUpdate: false, oldVersion: app.getVersion(), newVersion: arg?.version }) - }) - - const startDownload = (callback: any, successCallback: any) => { - // Monitor the download progress and push it to the update window - autoUpdater.on('download-progress', (data) => { - console.log("progress", data) - win.webContents.send('download-progress-data', data) - callback && callback instanceof Function && callback(null, data); - }); - // Listen for download errors and push to the update window - autoUpdater.on('error', (err) => { - callback && callback instanceof Function && callback(err); - }); - // Listen to the download completion and push it to the update window - autoUpdater.on('update-downloaded', () => { - NEED_INSTALL = true; - successCallback && successCallback instanceof Function && successCallback(); - }); - - autoUpdater.downloadUpdate(); - }; - - // Listen to the process message sent by the application layer and start downloading updates - ipcMain.on('start-download', (event) => { - console.log("start") - startDownload( - (err: any, progressInfo: { percent: any; }) => { - if (err) { - // callback download error message - event.sender.send('update-error', { updateError:true}); - } else { - // callback update progress message - event.sender.send('update-progress', { progressInfo: progressInfo.percent }); - } - }, - () => { - // callback update downed message - event.sender.send('update-downed'); - } - ); - }); - - // install now - ipcMain.on('quit-and-install', () => { - autoUpdater.quitAndInstall(false, true); - }) - - // install later - app.on('will-quit', () => { - console.log("NEED_INSTALL=true") - if (NEED_INSTALL) { - autoUpdater.quitAndInstall(true, false); - } - }); - -} \ No newline at end of file diff --git a/package.json b/package.json index a373bcc..7d217c8 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,9 @@ "pree2e": "vite build --mode=test", "e2e": "playwright test" }, + "dependencies": { + "electron-updater": "^5.3.0" + }, "devDependencies": { "@playwright/test": "^1.31.0", "@types/react": "^18.0.28", @@ -36,8 +39,5 @@ }, "engines": { "node": "^14.18.0 || >=16.0.0" - }, - "dependencies": { - "electron-updater": "^5.3.0" } } diff --git a/src/App.tsx b/src/App.tsx index c4c091d..6566091 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,11 +1,10 @@ import nodeLogo from './assets/node.svg' import { useState } from 'react' -import './App.scss' import Update from '@/components/update' +import './App.scss' console.log('[App.tsx]', `Hello world from Electron ${process.versions.electron}!`) - function App() { const [count, setCount] = useState(0) return ( @@ -30,6 +29,7 @@ function App() {
Place static files into the/public folder Node logo
+ ) diff --git a/src/components/update/Modal/index.tsx b/src/components/update/Modal/index.tsx index 7ecce0b..c8d6922 100644 --- a/src/components/update/Modal/index.tsx +++ b/src/components/update/Modal/index.tsx @@ -1,62 +1,67 @@ -import { createPortal } from 'react-dom'; -import { ModalChildType, ModalPropsType } from './type'; -import modalScss from './modal.module.scss' -const ModalTemplate = (child: ModalChildType) => { - return ( -
-
-
-
- {child.isHeaderShow ? ( -
-
{child.titleText}
- - - -
- ) : null} +import React, { ReactNode } from 'react' +import { createPortal } from 'react-dom' +import styles from './modal.module.scss' -
{child.body}
- {child.isFooterShow ? ( +const ModalTemplate: React.FC void + onOk?: () => void + width?: number +}>> = props => { + const { + title, + children, + footer, + cancelText = 'Cancel', + okText = 'OK', + onCancel, + onOk, + width = 530, + } = props + + return ( +
+
+
+
+
+
{title}
+ + + + + + +
+
{children}
+ {typeof footer !== 'undefined' ? (
- {(child.isSubmitShow ?? true) ? : null} - {(child.isCanCelShow ?? true) ? : null} + +
- ) : null} -
+ ) : footer} +
- ); -}; + ) +} -const Modal = (props: ModalPropsType) => { - return createPortal( - props.isOpenModal? - ModalTemplate({ - titleText: props.titleText, - isHeaderShow: props.isHeaderShow ?? true, - isFooterShow: props.isFooterShow ?? true, - isCanCelShow: props.isCanCelShow ?? true, - isSubmitShow: props.isSubmitShow ?? true, - body: props.children, - submitText: props.submitText, - canCelText: props.canCelText, - onCanCel: props.onCanCel, - onSubmit: props.onSubmit, - }):
, - document.body, - ); -}; -export default Modal; +const Modal = (props: Parameters[0] & { open: boolean }) => { + const { open, ...omit } = props + + return createPortal( + open ? ModalTemplate(omit) : null, + document.body, + ) +} + +export default Modal diff --git a/src/components/update/Modal/modal.module.scss b/src/components/update/Modal/modal.module.scss index a9536de..7218815 100644 --- a/src/components/update/Modal/modal.module.scss +++ b/src/components/update/Modal/modal.module.scss @@ -1,63 +1,89 @@ -.modal{ - :global{ - .modal-bg { - width: 100vw; - height: 100vh; - position: fixed; - left: 0; - top: 0; - z-index: 9999; - background: rgba(0, 0, 0, 0.3); - } - - .modal-outboard { - position: absolute; - top: 20vh; - left: 30vw; - z-index: 10000; - } - - .modal-panel { - border: 1px solid #000000; - border-radius: 5px; - - .modal-header { - $titleheight: 38px; - width: 530px; - height: $titleheight; - line-height: $titleheight; - background-color: rgb(99, 153, 255); - display: flex; - - .modal-header-text { - text-align: center; - width: 480px; - } +.modal { + --primary-color: rgb(224, 30, 90); + + :global { + .modal-mask { + width: 100vw; + height: 100vh; + position: fixed; + left: 0; + top: 0; + z-index: 9; + background: rgba(0, 0, 0, 0.45); + } + + .modal-warp { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 19; + } + + .modal-content { + box-shadow: 0 0 10px -4px rgb(130, 86, 208); + overflow: hidden; + border-radius: 4px; + + .modal-header { + display: flex; + line-height: 38px; + background-color: var(--primary-color); + + .modal-header-text { + font-weight: bold; + width: 0; + flex-grow: 1; } - - .modal-body { - background-color: #ffffff; + } + + .modal-close { + width: 30px; + height: 30px; + margin: 4px; + line-height: 34px; + text-align: center; + cursor: pointer; + + svg { + width: 17px; + height: 17px; } - - .modal-footer { - background-color: #ffffff; - display: flex; - justify-content: end; - - button { - margin: 10px; + } + + .modal-body { + padding: 10px; + background-color: #fff; + color: #333; + } + + .modal-footer { + padding: 10px; + background-color: #fff; + display: flex; + justify-content: end; + + button { + padding: 7px 11px; + background-color: var(--primary-color); + font-size: 14px; + margin-left: 10px; + + &:first-child { + margin-left: 0; } } } - - .icon { - padding: 0 15px; - width: 20px; - fill: currentColor; - - &:hover { - color: rgba(0, 0, 0, 0.4); - } + } + + .icon { + padding: 0 15px; + width: 20px; + fill: currentColor; + + &:hover { + color: rgba(0, 0, 0, 0.4); } + } } -} \ No newline at end of file +} diff --git a/src/components/update/Modal/type.d.ts b/src/components/update/Modal/type.d.ts deleted file mode 100644 index 2ba43c3..0000000 --- a/src/components/update/Modal/type.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ReactNode } from 'react' -interface childrens { - titleText?: string - isHeaderShow?: boolean - isFooterShow?: boolean - isCanCelShow?: boolean - isSubmitShow?: boolean - canCelText?: string - submitText?: string - onSubmit?: () => void - onCanCel?: () => void -} -export interface ModalChildType extends childrens { - body: ReactNode | null -} - -export interface ModalPropsType extends childrens { - isOpenModal: boolean - children: ReactNode | null -} diff --git a/src/components/update/Progress/index.tsx b/src/components/update/Progress/index.tsx index bb82162..06d5ed0 100644 --- a/src/components/update/Progress/index.tsx +++ b/src/components/update/Progress/index.tsx @@ -1,22 +1,22 @@ -import { RsProgressType } from './type' -import progressScss from './progress.module.scss' +import React from 'react' +import styles from './progress.module.scss' -const Progress = (props: RsProgressType) => { +const Progress: React.FC> = props => { + const { percent = 0 } = props return ( -
-
-
+
+
+
+
+ {percent}%
- {props.percent > 100 ? 100 :(props.percent.toString().substring(0,4) ?? 0) }% -
- ); -}; + ) +} -export default Progress; +export default Progress diff --git a/src/components/update/Progress/progress.module.scss b/src/components/update/Progress/progress.module.scss index ff84486..1a66779 100644 --- a/src/components/update/Progress/progress.module.scss +++ b/src/components/update/Progress/progress.module.scss @@ -4,7 +4,7 @@ :global { .progress-pr { - border: 1px solid #000000; + border: 1px solid #000; border-radius: 3px; height: 6px; } @@ -12,10 +12,11 @@ .progress-rate { height: 6px; border-radius: 3px; + background-image: linear-gradient(to right, rgb(130, 86, 208) 0%, var(--primary-color) 100%) } .progress-num { margin: 0 10px; } } -} \ No newline at end of file +} diff --git a/src/components/update/Progress/type.d.ts b/src/components/update/Progress/type.d.ts deleted file mode 100644 index 24a0970..0000000 --- a/src/components/update/Progress/type.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface RsProgressType { - rateColor?: string - rateWidth?: number - percent: number -} diff --git a/src/components/update/electron-updater.d.ts b/src/components/update/electron-updater.d.ts new file mode 100644 index 0000000..5b26aba --- /dev/null +++ b/src/components/update/electron-updater.d.ts @@ -0,0 +1,10 @@ +interface VersionInfo { + update: boolean + version: string + newVersion?: string +} + +interface ErrorType { + message: string + error: Error +} diff --git a/src/components/update/index.tsx b/src/components/update/index.tsx index 3eb4fd0..796ccaf 100644 --- a/src/components/update/index.tsx +++ b/src/components/update/index.tsx @@ -1,123 +1,135 @@ +import { ipcRenderer } from 'electron' +import type { ProgressInfo } from 'electron-updater' +import { useCallback, useEffect, useState } from 'react' import Modal from '@/components/update/Modal' import Progress from '@/components/update/Progress' -import { ipcRenderer } from 'electron' -import { useEffect, useState } from 'react' -import updateScss from './update.module.scss' -import { checkUpdateType, isUpdateAvailable, ModalBtnText, VersionInfo } from './type' - - -let onModalSubmit = () => { } -let onModalCanCel = () => { } +import styles from './update.module.scss' const Update = () => { - const [checkBtnText, setCheckBtnText] = useState('check update') - const [checkType, setCheckType] = useState(false) - const [checkLoading, setCheckLoading] = useState(false) - const [isOpenModal, setIsOpenModal] = useState(false) - const [percentNum, setPercentNum] = useState(0) - const [isNeedUpdate, setIsNeedUpdate] = useState(false) - const [updateError, setUpdateError] = useState(false) - const [versionInfo, setVersionInfo] = useState({ - oldVersion: '', - newVersion: '' - }) - const [modalBtnText, setModalBtnText] = useState({ - canCelText: '', - submitText: '' + const [checking, setChecking] = useState(false) + const [updateAvailable, setUpdateAvailable] = useState(false) + const [versionInfo, setVersionInfo] = useState() + const [updateError, setUpdateError] = useState() + const [progressInfo, setProgressInfo] = useState() + const [modalOpen, setModalOpen] = useState(false) + const [modalBtn, setModalBtn] = useState<{ + cancelText?: string + okText?: string + onCancel?: () => void + onOk?: () => void + }>({ + onCancel: () => setModalOpen(false), + onOk: () => ipcRenderer.invoke('start-download'), }) - useEffect(() => { - onModalCanCel = () => setIsOpenModal(false) - }, []) + const checkUpdate = async () => { + setChecking(true) + /** + * @type {import('electron-updater').UpdateCheckResult | null | { message: string, error: Error }} + */ + const result = await ipcRenderer.invoke('check-update') + setChecking(false) - // Check for updates - const checkUpdate = () => { - setCheckLoading(true) - setCheckBtnText('checking Update ...') - ipcRenderer.send('check-update') + if (result?.error) { + console.error(result.error) + setUpdateAvailable(false) + } else { + setUpdateAvailable(true) + setModalOpen(true) + } } - // Listen to get the check result - ipcRenderer.on('check-update-type', (_event, ...args: checkUpdateType[]) => { - setCheckLoading(false) - setCheckBtnText('check update') - setCheckType(args[0].checkUpdate) - setIsOpenModal(true) - }) - - // Get version information and whether to update - ipcRenderer.on('is-update-available', (_event, ...args: isUpdateAvailable[]) => { - setVersionInfo({ - oldVersion: args[0].oldVersion, - newVersion: args[0].newVersion, - }) - setIsNeedUpdate(args[0].isUpdate) - // Update required - if (args[0].isUpdate) { - setModalBtnText({ - canCelText: 'cancel', - submitText: 'update' - }) - onModalSubmit = () => ipcRenderer.send('start-download') - onModalCanCel = () => setIsOpenModal(false) + const onUpdateCanAvailable = useCallback((_event: Electron.IpcRendererEvent, arg1: VersionInfo) => { + setVersionInfo(arg1) + // Can be update + if (arg1.update) { + setModalBtn(state => ({ + ...state, + cancelText: 'Cancel', + okText: 'Update', + onCancel: () => setModalOpen(false), + onOk: () => ipcRenderer.invoke('start-download'), + })) } - }) + }, []) - // Throw the update failure message when the update fails - ipcRenderer.on('update-error', (_event, ...args: { updateError: boolean }[]) => { - setUpdateError(args[0].updateError) - setCheckType(false) - }) + const onUpdateError = useCallback((_event: Electron.IpcRendererEvent, arg1: ErrorType) => { + console.error(arg1.error) + setUpdateError(arg1) + }, []) - // Get update progress - ipcRenderer.on('update-progress', (_event, ...args: { progressInfo: number }[]) => { - setPercentNum(args[0].progressInfo) - }) + const onDownloadProgress = useCallback((_event: Electron.IpcRendererEvent, arg1: ProgressInfo) => { + console.log(arg1) + setProgressInfo(arg1) + }, []) - // is update been completed - ipcRenderer.on('update-downed', (_event, ...args) => { - setPercentNum(100) - setModalBtnText({ - canCelText: 'install later', - submitText: 'install now' - }) - onModalSubmit = () => ipcRenderer.send('quit-and-install') - onModalCanCel = () => { - ipcRenderer.send('will-quit') - setIsOpenModal(false) + const onUpdateDownloaded = useCallback((_event: Electron.IpcRendererEvent, ...args: any[]) => { + setModalBtn(state => ({ + ...state, + cancelText: 'Later', + okText: 'Install now', + onOk: () => ipcRenderer.invoke('quit-and-install'), + })) + }, []) + + useEffect(() => { + // Get version information and whether to update + ipcRenderer.on('update-can-available', onUpdateCanAvailable) + ipcRenderer.on('update-error', onUpdateError) + ipcRenderer.on('download-progress', onDownloadProgress) + ipcRenderer.on('update-downloaded', onUpdateDownloaded) + + return () => { + ipcRenderer.off('update-can-available', onUpdateCanAvailable) + ipcRenderer.off('update-error', onUpdateError) + ipcRenderer.off('download-progress', onDownloadProgress) + ipcRenderer.off('update-downloaded', onUpdateDownloaded) } - }) + }, []) return ( <> - -
- {updateError ? -
Error downloading the latest version, please contact the developer
: - checkType ? ( - isNeedUpdate ? ( -
-
- oldVersion : v.{versionInfo.oldVersion} - newVersion : v.{versionInfo.newVersion} + +
+ {updateError ? ( +
+

Error downloading the latest version.

+

{updateError.message}

+
+ ) : null} + {!updateAvailable ? ( +
+ Check update is Error, Please check your Network! +
+ ) : null} + {versionInfo + ? ( +
+
The last version is: v{versionInfo.newVersion}
+
v{versionInfo.version} -> v{versionInfo.newVersion}
+
+
Update progress:
+
+
-
- update progress : - -
-
) - : This is last version : v.{versionInfo.oldVersion} ! - ) : Check update is Error,Please check your network! - } +
+
+ ) + : Checking...}
- ) } -export default Update \ No newline at end of file +export default Update diff --git a/src/components/update/type.d.ts b/src/components/update/type.d.ts deleted file mode 100644 index de30813..0000000 --- a/src/components/update/type.d.ts +++ /dev/null @@ -1,18 +0,0 @@ - -export interface checkUpdateType { - checkUpdate: boolean -} - -export interface VersionInfo { - oldVersion: string - newVersion: string -} - -export interface isUpdateAvailable extends VersionInfo { - isUpdate: boolean - -} -export interface ModalBtnText { - canCelText: string - submitText: string -} diff --git a/src/components/update/update.module.scss b/src/components/update/update.module.scss index 926bb44..8ef5b92 100644 --- a/src/components/update/update.module.scss +++ b/src/components/update/update.module.scss @@ -1,19 +1,20 @@ -.modalslot{ - display: flex; - align-items: center; - justify-content: center; - height: 100px; +.modalslot { + // display: flex; + // align-items: center; + // justify-content: center; :global { - .progress-title { - width: 150px; - } - .update-progress { display: flex; } + + .progress-title { + margin-right: 10px; + } + + .progress-bar { + width: 0; + flex-grow: 1; + } } } -.a{ - color: red; -} \ No newline at end of file