Merge 1f8b17810a
into 29d5fa95a8
This commit is contained in:
commit
3143c80028
|
@ -33,5 +33,10 @@
|
|||
"perMachine": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"deleteAppDataOnUninstall": false
|
||||
},
|
||||
publish:{
|
||||
provider: 'generic',
|
||||
channel: 'latest',
|
||||
url: 'https://github.com/electron-vite/electron-vite-react/releases/download/v0.9.9/',
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import { app, BrowserWindow, shell, ipcMain } from 'electron'
|
||||
import { release } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { update } from '../preload/update'
|
||||
|
||||
// The built directory structure
|
||||
//
|
||||
|
@ -72,6 +73,7 @@ async function createWindow() {
|
|||
if (url.startsWith('https:')) shell.openExternal(url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
update(win)
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
|
@ -114,3 +116,4 @@ ipcMain.handle('open-win', (_, arg) => {
|
|||
childWindow.loadFile(indexHtml, { hash: arg })
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
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);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
|
@ -36,5 +36,8 @@
|
|||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-updater": "^5.3.0"
|
||||
}
|
||||
}
|
||||
|
|
20
src/App.tsx
20
src/App.tsx
|
@ -1,21 +1,22 @@
|
|||
import nodeLogo from "./assets/node.svg"
|
||||
import nodeLogo from './assets/node.svg'
|
||||
import { useState } from 'react'
|
||||
import './App.scss'
|
||||
import Update from '@/components/update'
|
||||
|
||||
console.log('[App.tsx]', `Hello world from Electron ${process.versions.electron}!`)
|
||||
|
||||
|
||||
function App() {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<div className='App'>
|
||||
<div>
|
||||
<a href="https://github.com/electron-vite/electron-vite-react" target="_blank">
|
||||
<img src="./electron-vite.svg" className="logo" alt="Electron + Vite logo" />
|
||||
<a href='https://github.com/electron-vite/electron-vite-react' target='_blank'>
|
||||
<img src='./electron-vite.svg' className='logo' alt='Electron + Vite logo' />
|
||||
</a>
|
||||
</div>
|
||||
<h1>Electron + Vite + React</h1>
|
||||
<div className="card">
|
||||
<div className='card'>
|
||||
<button onClick={() => setCount((count) => count + 1)}>
|
||||
count is {count}
|
||||
</button>
|
||||
|
@ -23,12 +24,13 @@ function App() {
|
|||
Edit <code>src/App.tsx</code> and save to test HMR
|
||||
</p>
|
||||
</div>
|
||||
<p className="read-the-docs">
|
||||
<p className='read-the-docs'>
|
||||
Click on the Electron + Vite logo to learn more
|
||||
</p>
|
||||
<div className="flex-center">
|
||||
Place static files into the<code>/public</code> folder <img style={{ width: "5em" }} src={nodeLogo} alt="Node logo" />
|
||||
<div className='flex-center'>
|
||||
Place static files into the<code>/public</code> folder <img style={{ width: '5em' }} src={nodeLogo} alt='Node logo' />
|
||||
</div>
|
||||
<Update />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import { createPortal } from 'react-dom';
|
||||
import { ModalChildType, ModalPropsType } from './type';
|
||||
import modalScss from './modal.module.scss'
|
||||
const ModalTemplate = (child: ModalChildType) => {
|
||||
return (
|
||||
<div className={modalScss.modal}>
|
||||
<div className='modal-bg' onClick={child.onCanCel} />
|
||||
<div className='modal-outboard'>
|
||||
<div className='modal-panel'>
|
||||
{child.isHeaderShow ? (
|
||||
<div className='modal-header'>
|
||||
<div className='modal-header-text'>{child.titleText}</div>
|
||||
<svg
|
||||
onClick={child.onCanCel}
|
||||
className='icon'
|
||||
viewBox='0 0 1026 1024'
|
||||
version='1.1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path
|
||||
d='M585.781589 510.748226l423.38657-423.38657A51.071963
|
||||
51.071963 0 0 0 937.156692 14.839469L513.770122 438.736759
|
||||
90.383552 14.839469A51.071963 51.071963 0 0 0 17.861365 87.361656L441.758655
|
||||
510.748226l-423.89729 423.38657A51.071963 51.071963 0 1 0 89.872832 1006.146263l423.89729-423.38657
|
||||
423.38657 423.38657a51.071963 51.071963 0 0 0 72.011467-72.011467z'
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className='modal-body'>{child.body}</div>
|
||||
{child.isFooterShow ? (
|
||||
<div className='modal-footer'>
|
||||
{(child.isSubmitShow ?? true) ?<button onClick={child.onSubmit}>{child.submitText ?? '确认'}</button> : null}
|
||||
{(child.isCanCelShow ?? true) ? <button onClick={child.onCanCel}>{child.canCelText ?? '取消'}</button> : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
}): <div></div>,
|
||||
document.body,
|
||||
);
|
||||
};
|
||||
export default Modal;
|
|
@ -0,0 +1,63 @@
|
|||
.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-body {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
|
||||
button {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding: 0 15px;
|
||||
width: 20px;
|
||||
fill: currentColor;
|
||||
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
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
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { RsProgressType } from './type'
|
||||
import progressScss from './progress.module.scss'
|
||||
|
||||
const Progress = (props: RsProgressType) => {
|
||||
|
||||
return (
|
||||
<div className={progressScss.progress}>
|
||||
<div className='progress-pr' style={{ width: props.rateWidth ?? 250 }}>
|
||||
<div
|
||||
className='progress-rate'
|
||||
style={{
|
||||
width: (props.percent ?? 0) * ((props.rateWidth ?? 250) / 100),
|
||||
backgroundColor: props.rateColor ?? 'blue',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className='progress-num'>{props.percent > 100 ? 100 :(props.percent.toString().substring(0,4) ?? 0) }%</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Progress;
|
|
@ -0,0 +1,21 @@
|
|||
.progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
:global {
|
||||
.progress-pr {
|
||||
border: 1px solid #000000;
|
||||
border-radius: 3px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.progress-rate {
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.progress-num {
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export interface RsProgressType {
|
||||
rateColor?: string
|
||||
rateWidth?: number
|
||||
percent: number
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
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 = () => { }
|
||||
|
||||
const Update = () => {
|
||||
const [checkBtnText, setCheckBtnText] = useState('check update')
|
||||
const [checkType, setCheckType] = useState(false)
|
||||
const [checkLoading, setCheckLoading] = useState(false)
|
||||
const [isOpenModal, setIsOpenModal] = useState<boolean>(false)
|
||||
const [percentNum, setPercentNum] = useState<number>(0)
|
||||
const [isNeedUpdate, setIsNeedUpdate] = useState<boolean>(false)
|
||||
const [updateError, setUpdateError] = useState<boolean>(false)
|
||||
const [versionInfo, setVersionInfo] = useState<VersionInfo>({
|
||||
oldVersion: '',
|
||||
newVersion: ''
|
||||
})
|
||||
const [modalBtnText, setModalBtnText] = useState<ModalBtnText>({
|
||||
canCelText: '',
|
||||
submitText: ''
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
onModalCanCel = () => setIsOpenModal(false)
|
||||
}, [])
|
||||
|
||||
// Check for updates
|
||||
const checkUpdate = () => {
|
||||
setCheckLoading(true)
|
||||
setCheckBtnText('checking Update ...')
|
||||
ipcRenderer.send('check-update')
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
})
|
||||
|
||||
// Throw the update failure message when the update fails
|
||||
ipcRenderer.on('update-error', (_event, ...args: { updateError: boolean }[]) => {
|
||||
setUpdateError(args[0].updateError)
|
||||
setCheckType(false)
|
||||
})
|
||||
|
||||
// Get update progress
|
||||
ipcRenderer.on('update-progress', (_event, ...args: { progressInfo: number }[]) => {
|
||||
setPercentNum(args[0].progressInfo)
|
||||
})
|
||||
|
||||
// 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)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal isOpenModal={isOpenModal} onCanCel={onModalCanCel} onSubmit={onModalSubmit}
|
||||
canCelText={modalBtnText.canCelText} submitText={modalBtnText.submitText}
|
||||
isFooterShow={checkType && isNeedUpdate}>
|
||||
<div className={updateScss.modalslot}>
|
||||
{updateError ?
|
||||
<div className='update-error'>Error downloading the latest version, please contact the developer</div> :
|
||||
checkType ? (
|
||||
isNeedUpdate ? (
|
||||
<div>
|
||||
<div>
|
||||
<span> oldVersion : v.{versionInfo.oldVersion} </span>
|
||||
<span> newVersion : v.{versionInfo.newVersion} </span>
|
||||
</div>
|
||||
<div className='update-progress'>
|
||||
<span className='progress-title'> update progress : </span>
|
||||
<Progress percent={percentNum} ></Progress>
|
||||
</div>
|
||||
</div>)
|
||||
: <span>This is last version : v.{versionInfo.oldVersion} !</span>
|
||||
) : <span>Check update is Error,Please check your network!</span>
|
||||
}
|
||||
</div>
|
||||
</Modal>
|
||||
<button disabled={checkLoading} onClick={checkUpdate}>
|
||||
{checkBtnText}
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Update
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
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
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
.modalslot{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100px;
|
||||
|
||||
:global {
|
||||
.progress-title {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.update-progress {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
.a{
|
||||
color: red;
|
||||
}
|
Loading…
Reference in New Issue